Custom non-blocking pop-up in Flutter with scrollable content - ios

I'm trying to build a custom pop-up in Flutter/Dart using the following features:
Non-blocking. The popup should be triggered from a scrollable view and should not block the view from being scrolled after the popup is shown.
The popup should contain custom content that supports pushing and popping pages inside the popup, including scrollable views.
The popup should support an arrow pointing to a specific location on the screen where it was triggered from.
Please refer to the attached images for an example of this implementation in IOS. I desire to achieve the same thing using Flutter/Dart.
The popups included in Flutter framework are rudimentary. Any ideas on how this is done?

I'm not a flutter pro at all but just out of curiosity I tried to implement as much as I can of your targets in my free time.
You can show a popup without blocking the scroll. And you can push and pop the content of popup.
I'm not claiming that it's an efficient or a healthy way to do things. It's not complete but I think you can go on and build on it. If you decide to use it I think it would be best to;
Edit the PopupBase showPopup method to avoid misplacement of popup as it can overflow in certain situations.
The arrow which will point to the clicked location can also be added after the improvement of placement.
class MyScaffold extends StatefulWidget {
const MyScaffold({Key? key}) : super(key: key);
#override
State<MyScaffold> createState() => _MyScaffoldState();
}
class _MyScaffoldState extends State<MyScaffold> {
CustomPopupController customPopupController = CustomPopupController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("NonBlocking PopUp")),
body: Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: PopupBase(
child: RichTextContent(),
)),
),
),
],
),
);
}
}
class PopupBase extends StatefulWidget {
const PopupBase({Key? key, required this.child}) : super(key: key);
final Widget child;
#override
State<PopupBase> createState() => _PopupBaseState();
static _PopupBaseState of(BuildContext context) =>
context.findAncestorStateOfType<_PopupBaseState>()!;
}
class _PopupBaseState extends State<PopupBase> {
List<Widget> popupStack = List.empty(growable: true);
#override
void initState() {
super.initState();
popupStack.add(widget.child);
}
void showPopup(CustomPopup popup, Offset position) {
setState(() {
popupStack.add(Positioned(
child: popup,
left: position.dx,
top: position.dy,
));
});
}
void removePopup(CustomPopup popup) {
print("popupbase remove");
setState(() {
popupStack.removeLast();
});
print(popupStack.length);
}
#override
Widget build(BuildContext context) {
return Stack(
children: popupStack,
);
}
}
class CustomPopupController {
late bool Function(Widget) push;
late Widget Function()? pop;
}
class CustomPopup extends StatefulWidget {
CustomPopup({Key? key, required this.content, required this.controller})
: super(key: key);
Widget content;
final CustomPopupController controller;
#override
State<CustomPopup> createState() => _CustomPopupState();
static _CustomPopupState of(BuildContext context) =>
context.findAncestorStateOfType<_CustomPopupState>()!;
}
class _CustomPopupState extends State<CustomPopup> {
#override
void initState() {
super.initState();
widget.controller.push = push;
widget.controller.pop = pop;
}
final List<Widget> _contentStack = List.empty(growable: true);
bool push(Widget widgetToPush) {
_contentStack.add(widget.content);
setState(() {
widget.content = widgetToPush;
});
return false;
}
Widget pop() {
if (_contentStack.isNotEmpty) {
Widget temp = widget.content;
setState(() {
widget.content = _contentStack.last;
_contentStack.removeLast();
});
return temp;
} else {
PopupBase.of(context).removePopup(this.widget);
return widget.content;
}
}
#override
Widget build(BuildContext context) {
return Card(
child: SizedBox(
height: 300,
width: 200,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
color: Colors.red,
height: 25,
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
(_contentStack.isNotEmpty)
? Padding(
padding: EdgeInsets.only(left: 10),
child: InkWell(
onTap: () {
pop();
},
child: Icon(Icons.arrow_back_ios)),
)
: Container(),
Expanded(child: Container()),
Padding(
padding: EdgeInsets.only(left: 10),
child: InkWell(
onTap: () {
pop();
},
child: Icon(Icons.close)),
)
],
),
),
Expanded(
child: SingleChildScrollView(
child: widget.content,
))
],
)),
);
}
}
class RichTextContent extends StatelessWidget {
RichTextContent({Key? key}) : super(key: key);
TextStyle text = TextStyle(color: Colors.black);
TextStyle clickable = TextStyle(color: Colors.blue);
#override
Widget build(BuildContext context) {
CustomPopupController controller = CustomPopupController();
return RichText(
text: TextSpan(children: [
TextSpan(style: text, text: """Lorem ipsum dolor sit """),
TextSpan(
text: 'amet',
style: clickable,
recognizer: TapGestureRecognizer()
..onTapUp = (TapUpDetails details) {
PopupBase.of(context).showPopup(
CustomPopup(content: Text("Popup"), controller: controller),
details.localPosition);
}),
TextSpan(
style: text,
text:
""", consectetur adipiscing elit. Nulla bibendum at massa et euismod. Praesent ex ipsum, ultrices ut rhoncus et, efficitur vehicula mi. Duis sollicitudin dolor sed tristique molestie. Ut eu elit velit. Cras et lorem quis risus mattis porta vitae ac velit. Nunc laoreet malesuada lectus at laoreet. Etiam ut tristique nulla. Pellentesque eros est, pretium sit amet convallis ut, convallis eu justo. Nulla suscipit blandit massa. Vestibulum vitae magna eu urna faucibus hendrerit. Etiam eros nibh, venenatis ac ullamcorper vitae, venenatis ac neque. Aenean vitae erat massa. Aliquam vulputate facilisis volutpat. Sed tincidunt dolor a enim dictum, eget tristique nisi laoreet. Quisque suscipit, odio et mattis mattis, lectus justo lacinia enim, nec finibus mi ante gravida sem.
Nunc ac nunc nec sapien porttitor volutpat. In et accumsan est. Duis non dui porta, pharetra dolor consequat, bibendum lorem. Quisque suscipit sit amet mi ac placerat. Integer cursus, est nec aliquet consequat, felis erat sodales sem, eget maximus ante libero in nibh. Cras et orci magna. Suspendisse viverra nibh eget nulla mattis laoreet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi tortor diam, pulvinar quis erat a, posuere vehicula ante. Sed aliquet et magna nec finibus. Morbi in dapibus risus. Nullam ac imperdiet enim.
Nam enim mauris, volutpat et metus ut, euismod porttitor odio. Duis at orci hendrerit, posuere tortor sed, convallis nibh. Nullam lobortis est eget magna finibus porttitor. Nulla facilisi. Donec bibendum ac lorem eget consectetur. Phasellus at lacinia augue. Integer a mi quam. Morbi malesuada maximus diam. In orci nisi, mollis sed urna eget, fermentum efficitur libero. Cras auctor est sit amet ex aliquet, eu rutrum turpis gravida. Donec mattis, erat eget ultricies accumsan, odio urna egestas lectus, at mattis lorem sem scelerisque dolor. Curabitur aliquet venenatis bibendum.
"""),
]));
}
}

You can find a live example of this. Using selectable plugin.
It is not an easy task but you can Modify this code to do what you want.

Related

Bug with stain lines when using ScrollView in SwiftUI

Create some view and put it in ScrollView.
Important: The view should be less than iPhone screen bounds to observe the bug. If we make ScrollView full-screen (from left to right bounds) the bug isn't observed.
The bug is observed on any simulator model.
Platform: iOS 15.4
Xcode: Version 13.3 (13E113)
Machine: MacBook Air M1 2020
Pictures (watch the white lines on the right and on the bottom):
Sample code:
import SwiftUI
struct ContentView: View {
var text: String {
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ultrices nec erat ut dictum. Mauris non urna ac leo eleifend commodo ac eu neque. Aenean tellus orci, dictum vel ullamcorper eu, condimentum vitae arcu. Vivamus consectetur sem tellus, in efficitur ante fringilla non. Nam eleifend dui vel felis mattis, eu mattis nunc fermentum. Curabitur feugiat arcu convallis, sodales felis vel, condimentum ipsum. Curabitur auctor urna mauris, sit amet porttitor justo cursus at. Mauris condimentum mi ac velit sollicitudin, ac commodo tortor imperdiet. Vestibulum consectetur, purus quis vulputate condimentum, libero libero imperdiet massa, in imperdiet enim dolor at erat. Nulla sapien dui, maximus id rhoncus eu, luctus at tellus. Praesent rutrum velit vel libero sodales eleifend. Phasellus in condimentum lectus.
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent et sapien sapien. Sed dapibus mi vitae porttitor vulputate. Suspendisse vel erat blandit, pellentesque leo vitae, mattis leo. Pellentesque dictum ex sit amet nibh molestie, in hendrerit nibh accumsan. Sed mollis neque quam, rhoncus elementum ex efficitur eget. Nunc quis tristique leo. Nullam sed rhoncus massa, quis blandit nisi. In pharetra pretium eros, non convallis felis mattis in. Sed eleifend elit risus, in tincidunt massa varius id. Morbi velit nunc, faucibus eget est eu, porttitor pharetra mauris. Nam aliquam ligula diam, nec commodo elit commodo ut. Nunc consectetur fermentum est, placerat eleifend neque dictum id.
Nullam et lobortis metus. In eget metus lacinia, bibendum augue eget, elementum nibh. Quisque tempor lorem nec purus dapibus finibus. Vivamus dui enim, viverra et mattis quis, dictum sit amet erat. Mauris nec purus hendrerit, elementum nibh id, mollis urna. Nulla molestie mi id turpis egestas tincidunt. Praesent porttitor, lorem nec venenatis gravida, libero est vulputate mauris, eu elementum sem augue eget magna. Pellentesque vehicula mauris non nibh volutpat vestibulum. Vestibulum auctor dictum sapien a fringilla. Quisque sed mauris ex. Maecenas at tortor sit amet ligula pretium vehicula. Pellentesque tempor lectus sed dui sollicitudin, ut luctus diam tincidunt. Morbi a vestibulum ante. Donec elementum sed lorem nec ornare. Nam varius dignissim ante. Nullam aliquam vitae magna sit amet viverra.
"""
}
var body: some View {
Background {
ScrollView(.vertical, showsIndicators: false) {
TextSheet {
Text(text)
.padding(24)
}
}
}
}
}
struct TextSheet<Content: View>: View {
private let content: () -> Content
var body: some View {
ZStack {
Rectangle()
.foregroundColor(.white)
content()
}
.padding([.leading, .trailing], 32)
}
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content
}
}
struct Background<Content: View>: View {
private let content: () -> Content
var body: some View {
ZStack {
Rectangle()
.foregroundColor(.purple)
.ignoresSafeArea()
content()
.clipped()
}
}
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content
}
}
Note: This is a simplified example to show the bug, I know there're better ways to implement text views.
If we enable scroll indicators the problem goes away:
...Background {
ScrollView(.vertical, showsIndicators: true) {
TextSheet {...
However, in our case, the problem resolves only in the vertical direction since this is the only direction which is being involved. Removing .vertical doesn't help either:
...Background {
ScrollView(showsIndicators: true) {
TextSheet {...
I suppose that this is a SwiftUI 3 bug, but I haven't found a workaround for this or the respective Apple Developer Forum discussion of the bug.

Keyboard is adding extra padding above

When I launch a keyboard (on iOS and Android) I have an extra 50 px padding added above it which shows an empty space.
This isn't caused by other suggested problems where there are multiple Scaffold widgets. I know that this is caused by my layout and I'm not sure if it can be fixed at the moment.
I have a static navigation bar that appears across the app at the bottom. This is 50px high and sits below CupertinoApp. When the keyboard is launched, extra padding is added to fill this space it seems. This navigation bar is a Container widget with InkWell links to open views directly, sort of like CupertinoTabScaffold except I don't have tabs, it just pushes or replaces the current navigation item
Adding resizeToAvoidBottomInset: false, can't be used as this removes the scroll action or doesn't include the 50px so content is hidden.
This problem only occurs when the keyboard is active
The CupertinoApp builder
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => Future<bool>.value(true),
child: Column(
children: <Widget>[
Expanded(
child: DefaultTextStyle(
style: Fonts.defaultFontStyle(),
child: CupertinoApp(
navigatorKey: navigatorKey,
routes: routes.builder,
)),
),
BottomBar(),
],
),
);
}
Adding a minimal example to get across a better idea
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
final NAVIGATION_HEIGHT = 50.0;
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
static const home = "/";
static const about = "/about";
static const products = "/products";
Map<String, WidgetBuilder> builder = {
home: (BuildContext context) => MyPage(),
about: (BuildContext context) => MyPage(),
products: (BuildContext context) => MyPage(),
};
Widget _createButtonsBuilder(int index) {
String pageTitle = builder.keys.toList()[index].substring(1);
if (pageTitle.length == 0) {
pageTitle = "Home";
}
return Expanded(
child: Material(
color: Colors.black87,
child: InkWell(
onTap: () {
navigatorKey.currentState.push(CupertinoPageRoute<void>(
builder: (BuildContext context) => MyPage(
title: "$pageTitle",
navigationHeight: NAVIGATION_HEIGHT,
),
));
},
child: Container(
padding: EdgeInsets.all(10.0),
height: NAVIGATION_HEIGHT,
child: Text(
"$pageTitle",
style: TextStyle(
inherit: true, color: Colors.white, fontSize: 20.0),
textAlign: TextAlign.center,
),
)),
),
);
}
Widget _BottomBar() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: List.generate(builder.keys.toList().length,
(int i) => _createButtonsBuilder(i)),
);
}
#override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.ltr,
child: Container(
color: Colors.white,
child: Column(
children: <Widget>[
Expanded(
child: CupertinoApp(
title: 'Flutter Demo',
routes: builder,
navigatorKey: navigatorKey,
),
),
_BottomBar()
],
),
),
);
}
}
class MyPage extends StatefulWidget {
MyPage({Key key, this.title, this.navigationHeight}) : super(key: key);
final String title;
final double navigationHeight;
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Scrollbar(
child: SingleChildScrollView(
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'${widget.title}',
),
SizedBox(height: 40.0),
Container(
margin: EdgeInsets.all(30.0),
child: CupertinoTextField(
placeholder: "Tap in here",
),
),
Container(
margin: EdgeInsets.all(30.0),
child: Text(
"When tapping to add focus to the above CupertinoTextField Widget the keyboard will appear as normal but because of the _BottomBar() outside the CupertinoApp the height (${widget.navigationHeight}px) is included in the padding above the keyboard")),
Container(
margin: EdgeInsets.all(30.0),
child: Text(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi interdum blandit diam sed faucibus. Donec ut enim in ante luctus sagittis. Donec vestibulum aliquet nunc, in efficitur erat molestie quis. In dictum aliquet neque. Vivamus pharetra nibh dictum urna sodales malesuada. Nunc porta condimentum mi, sed laoreet erat maximus vitae. Nunc luctus nisi urna, a luctus nisi consectetur quis. Sed rhoncus euismod nisl, in laoreet leo molestie sed. Suspendisse aliquet commodo dui, sit amet rhoncus sapien venenatis in. Nulla tempus libero diam, non cursus odio euismod at. Fusce nec ipsum ipsum. Mauris congue blandit risus, vitae pretium leo euismod id. Vivamus venenatis finibus diam id auctor. Donec vel urna finibus erat viverra bibendum. Vivamus sagittis eros id bibendum tristique. Nullam eleifend elit dapibus elit porttitor, ac egestas libero mollis.\n\nMorbi eleifend ligula sed leo placerat tristique. Mauris consequat fringilla maximus. Fusce pharetra ultrices risus, quis fermentum urna ultrices non. Vivamus suscipit nunc non ipsum ultrices laoreet. Etiam sed vestibulum eros, nec tempus neque. Vestibulum efficitur mauris ac ipsum aliquet, et tincidunt massa suscipit. Vivamus enim justo, viverra tempor purus eu, elementum tempor tortor. Sed rhoncus gravida sem, vitae molestie augue iaculis et. Donec augue ligula, interdum id interdum sit amet, condimentum in dolor. Donec dignissim erat lorem, ut accumsan felis porta id. Nunc lorem enim, maximus ut odio at, ultrices sodales velit."))
],
),
),
),
) // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
I wrapped my CupertinoApp in Scaffold widget which exposed bottomNavigationBar.
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => Future<bool>.value(true),
child: Scaffold(
body: DefaultTextStyle(
style: Fonts.defaultFontStyle(),
child: CupertinoApp(
navigatorKey: navigatorKey,
routes: routes.builder,
)),
),
bottomNavigationBar: HomeBottomBar(),
));
}
And then added resizeToAvoidBottomPadding = true to any CupertinoPageScaffold's that have CupertinoTextfield children

Return list from JSON call in dart

EDIT:
ENTIRE DART FILE -- Here is the entire dart file. Im not seeing how to call the method in the ARR declaration and return the correct mapping. Just need to generate the array from the http call and we are good. This is our first run at returning multiple records from JSON and iterating thru it.
import 'package:flutter/material.dart';
import 'package:lightbridge_mobile/screens/forum/assets/colors.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:lightbridge_mobile/screens/forum/assets/app_bar_forum.dart';
import 'package:lightbridge_mobile/models/forum_answers.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
class ForumDetailPage extends StatefulWidget {
#override
_ForumDetailPageState createState() => new _ForumDetailPageState();
}
class _ForumDetailPageState extends State<ForumDetailPage> {
#override
Widget build(BuildContext context) {
var questionSection = new Padding(
padding: const EdgeInsets.all(8.0),
child: new Column(
children: <Widget>[
new Text(
// Post Title
"How do I become a expert in programming as well as design ??",
textScaleFactor: 1.5,
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 15.0 ),
),
new Padding(
padding: const EdgeInsets.all(10.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
new IconWithText(Icons.laptop_windows, "Technology", iconColor: Colors.grey),
new IconWithText(
Icons.question_answer,
"Answered",
iconColor: Colors.grey,
),
new IconWithText(Icons.remove_red_eye, "54", iconColor: Colors.grey)
],
),
),
new Divider( height: 1.0)
],
),
);
var responses = new Container(
padding: const EdgeInsets.all(8.0),
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) => new ForumPost(ForumPostArr[index]),
itemCount: ForumPostArr.length,
)
);
return new Scaffold(
appBar : LBForumAppBar().getAppBar(),
body: new Column(
children: <Widget>[
questionSection,
new Expanded(
child: new Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: responses,
))
],
),
);
}
}
var ForumPostArr = [
new ForumPostEntry("User1", "2 Days ago", 0 , 0 , "Hello,\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
new ForumPostEntry("User2", "23 Hours ago", 1 , 0 , "Pellentesque justo metus, finibus porttitor consequat vitae, tincidunt vitae quam. Vestibulum molestie sem diam. Nullam pretium semper tempus. Maecenas lobortis lacus nunc, id lacinia nunc imperdiet tempor. Mauris mi ipsum, finibus consectetur eleifend a, maximus eget lorem. Praesent a magna nibh. In congue sapien sed velit mattis sodales. Nam tempus pulvinar metus, in gravida elit tincidunt in. Curabitur sed sapien commodo, fringilla tortor eu, accumsan est. Proin tincidunt convallis dolor, a faucibus sapien auctor sodales. Duis vitae dapibus metus. Nulla sit amet porta ipsum, posuere tempor tortor.\n\nCurabitur mauris dolor, cursus et mi id, mattis sagittis velit. Duis eleifend mi et ante aliquam elementum. Ut feugiat diam enim, at placerat elit semper vitae. Phasellus vulputate quis ex eu dictum. Cras sapien magna, faucibus at lacus vel, faucibus viverra lorem. Phasellus quis dui tristique, ultricies velit non, cursus lectus. Suspendisse neque nisl, vestibulum non dui in, vulputate placerat elit. Sed at convallis mauris, eu blandit dolor. Vivamus suscipit iaculis erat eu condimentum. Aliquam erat volutpat. Curabitur posuere commodo arcu vel consectetur."),
new ForumPostEntry("User3", "2 Days ago", 5 , 0 , "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
new ForumPostEntry("User4", "2 Days ago", 0 , 0 , "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
];
Future<List<ForumAnswers>> getForumAnswers(String postID) async {
final response =
await http.post('http://api/ForumAnswers',
headers: {"Content-Type": "application/json",
'Accept': 'application/json',},
body: json.encode({'PostID' : postID }));
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON
List l = json.decode(response.body);
List<ForumAnswers> posts = l.map((m) => ForumAnswers.fromJson(m)).toList();
return posts;
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load user');
}
}
class ForumPostEntry{
final String username;
final String hours;
final int likes;
final int dislikes;
final String text;
ForumPostEntry(this.username, this.hours, this.likes, this.dislikes, this.text);
}
class ForumPost extends StatelessWidget {
final ForumPostEntry entry;
ForumPost(this.entry);
#override
Widget build(BuildContext context) {
return new Container(
margin: const EdgeInsets.only(bottom: 10.0),
decoration: new BoxDecoration(
color: Colors.grey,
borderRadius: const BorderRadius.all(const Radius.circular(20.0)),
),
child: new Column(
children: <Widget>[
new Container(
decoration: new BoxDecoration(
color: Colors.grey[600],
borderRadius: const BorderRadius.only(
topLeft: const Radius.circular(20.0),
topRight: const Radius.circular(20.0)),
),
child: new Row(
children: <Widget>[
new Icon(
Icons.person,
size: 50.0,
color: Colors.white
),
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
entry.username
),
new Text(
entry.hours
),
],
),
),
new Row(
children: <Widget>[
new Padding(
padding: const EdgeInsets.all(2.0),
child: new Icon(Icons.thumb_up, color: Colors.white),
),
new Padding(
padding: const EdgeInsets.all(2.0),
child: new Text(entry.likes.toString()),
),
new Padding(
padding: const EdgeInsets.all(2.0),
child: new Icon(Icons.thumb_down, color: Colors.white),
),
new Padding(
padding: const EdgeInsets.only(right: 8.0, left: 2.0),
child: new Text(entry.dislikes.toString()),
),
],
)
],
),
),
new Container(
margin: const EdgeInsets.only(left: 2.0,right: 2.0,bottom: 2.0),
padding: const EdgeInsets.all(8.0),
decoration: new BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(bottomLeft :const Radius.circular(20.0),bottomRight :const Radius.circular(20.0))
),
child: new Text(entry.text),
),
],
),
);
}
}
class IconWithText extends StatelessWidget {
final IconData iconData;
final String text;
final Color iconColor;
IconWithText(this.iconData, this.text, {this.iconColor});
#override
Widget build(BuildContext context) {
return new Container(
child: new Row(
children: <Widget>[
new Icon(
this.iconData,
color: this.iconColor,
),
new Padding(
padding: const EdgeInsets.only(left: 8.0),
child: new Text(this.text),
),
],
),
);
}
}
Presumably, your json consists of an array at the outer most level; it's enclosed in [..].
l, the result of json.decode will be a List<dynamic>, though the list will happen to contain Maps.
You should find that this works better:
List l = json.decode(response.body);
List<ForumAnswers> posts = l.map((m) => ForumAnswers.fromJson(m)).toList();
Note that if you want to return posts you should change the signature to returning Future<List<ForumAnswers>>.
use these
factory ForumAnswers.fromJson(Map parsedjson) {
return ForumAnswers(
content: parsedjson['content'],
username: parsedjson['username'],
createDate : parsedjson['createDate'],
upvote : parsedjson['upvote'],
);
if not follow these link https://medium.com/flutter-community/parsing-complex-json-in-flutter-747c46655f51

Truncate text when it reaches next control

Consider the following code:
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new FlatButton(
child: new Container(
child: new Center(child: new Text("ABOVE")),
height: 300.0,
color: const Color.fromARGB(255, 255, 0, 0),
),
),
new Expanded(
child: new Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",)
),
new FlatButton(
child: new Container(
child: new Center(child: new Text("BELOW")),
height: 300.0,
color: const Color.fromARGB(255, 255, 0, 0),
),
),
],
);
}
}
On my Pixel, this produces:
My expectation was that the text would continue up until it reaches the bottom button, then clip. If I set overflow: TextOverflow.ellipsis then it truncates after the first line:
If I set maxLines: 3 then it continues until the fourth line:
However, I can't find a way to just continue until it reaches the bottom button.
Can anyone enlighten me?
There is no direct styling option that does this, but however you can do it by first calculating maxLines that can occupy the available view in runtime and then just specify overflow and maxLines properties.
To get available height, use a LayoutBuilder the provides the constrains
lineHeight = fontSize * textScaleFactor * lineHeightScaleFactor
maxlines = (available height/line height)
Example:
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Material(
child: new Column(
children: <Widget>[
new FlatButton(
child: new Container(
child: new Center(child: new Text("ABOVE")),
height: 300.0,
color: const Color.fromARGB(255, 255, 0, 0),
),
),
new Expanded(child: new LayoutBuilder(builder: (context, constrains) {
double lineScaleFactor = 1.1; // this is multiplied with fontsize to get lineHeight
TextStyle style = new TextStyle(fontSize: 16.0,height: lineScaleFactor);
double scale = 1.0;
double lineHeight = style.fontSize*scale*lineScaleFactor;
return new Container(
child: new Text(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
overflow: TextOverflow.ellipsis,
style: style,
textScaleFactor: scale,
maxLines: (constrains.maxHeight ~/ lineHeight),
),
);
})),
new FlatButton(
child: new Container(
child: new Center(child: new Text("BELOW")),
height: 300.0,
color: const Color.fromARGB(255, 255, 0, 0),
),
),
],
),
);
}
}
Hope that helped!

Webgl background to a transparent site

I would like to have a site with a webgl background to a transparent site.
What would be the safest way to achieve this given the various states of browsers?
I think you just want to make a canvas and set it's CSS so fills the background
<!DOCTYPE html>
<html>
<head>
<style>
/* make the canvas fill the page and not scroll */
#c {
position: fixed;
left: 0px;
top: 0px;
z-index: -10;
width: 100vw;
height: 100vh;
}
/* remove the margin on the body so the canvas goes to the edge */
body {
margin: 0;
}
/* make a new body with standard margins */
#body {
margin: 8px;
}
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="body">
<!-- insert rest of html here -->
</div>
</body>
</html>
Then just make sure the canvas is the correct size
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
// make the canvas match the size it's displayed.
var resize = function() {
var width = gl.canvas.clientWidth;
var height = gl.canvas.clientHeight;
if (gl.canvas.width != width || gl.canvas.height != height) {
gl.canvas.width = width;
gl.canvas.height = height;
}
};
var render = function() {
resize();
gl.clearColor(1, Math.random() * 0.2 + 0.8, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// insert rendering code here
requestAnimationFrame(render);
};
render();
This example just clears the canvas (simplest example). Here's a snippet
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
var resize = function() {
var width = gl.canvas.clientWidth;
var height = gl.canvas.clientHeight;
if (gl.canvas.width != width || gl.canvas.height != height) {
gl.canvas.width = width;
gl.canvas.height = height;
}
};
var render = function() {
resize();
gl.clearColor(1, Math.random() * 0.2 + 0.8, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
requestAnimationFrame(render);
};
render();
#c {
position: fixed;
left: 0px;
top: 0px;
z-index: -10;
width: 100vw;
height: 100vh;
}
body {
margin: 0;
}
#body {
margin: 8px;
}
<canvas id="c"></canvas>
<div id="body">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec gravida est, nec fermentum metus. Suspendisse congue ante elit, vitae accumsan quam convallis nec. Integer sodales, nibh id ultricies hendrerit, risus lacus varius ligula, quis porta nulla massa sit amet quam. Praesent posuere vulputate nunc, ac convallis nulla consectetur eget. Vestibulum eu dapibus justo. Aenean ac venenatis sem. Nullam porta, augue at egestas pretium, diam metus suscipit eros, eu eleifend quam diam in lacus. Fusce accumsan sem in placerat blandit. Nulla eget hendrerit lorem, at semper leo. Duis non commodo tellus.
</p><p>
Sed feugiat velit vel ipsum fermentum, a scelerisque dolor tincidunt. Vestibulum id odio ultrices metus consectetur vehicula. Nulla vitae metus sagittis mauris commodo euismod. In erat dui, vehicula in consectetur ut, mollis at dui. Vestibulum vulputate est eu tellus egestas ullamcorper. Aenean ut ligula lacinia, cursus est vitae, placerat nulla. Sed suscipit rutrum dolor, vitae feugiat orci eleifend in. Proin sit amet nisl purus. Curabitur eget sem nunc. Suspendisse a mattis libero, in bibendum purus. Pellentesque semper eros tincidunt libero aliquam, eget placerat dui consequat. Suspendisse potenti. Sed sed imperdiet metus, non rutrum tellus. Nunc egestas nec libero sodales interdum.
</p><p>
Quisque ultricies, enim ornare euismod vestibulum, neque velit volutpat magna, eget interdum leo nisl in leo. Cras tempor odio ut magna iaculis, at fermentum nulla semper. Etiam laoreet hendrerit gravida. Sed sit amet luctus nibh, sed sodales neque. Nunc varius fringilla nisl, sodales adipiscing sem gravida sed. Etiam ac suscipit turpis. Sed luctus adipiscing ipsum, et facilisis diam pulvinar eu. Etiam blandit id ante viverra varius. Vestibulum tincidunt nisi at velit tempor facilisis. Aenean bibendum fringilla dolor ac tincidunt. Nam facilisis vestibulum augue ut ultricies. Nam suscipit odio non orci lobortis, vitae porttitor purus ultricies. Aliquam egestas neque at lorem aliquam, ut vestibulum libero viverra.
</p><p>
Donec sed feugiat sapien. Cras vitae porta lorem. Pellentesque in enim eu elit vulputate laoreet. Sed non placerat velit. Mauris luctus est auctor, vestibulum orci a, vulputate nunc. Nunc suscipit, ante sit amet porttitor lacinia, dolor augue consectetur augue, ac auctor dui eros non est. Duis scelerisque eget sapien ac scelerisque. Aliquam et tellus ornare, facilisis sem a, luctus est. Aliquam vitae facilisis est. Ut tempor justo urna. Nam commodo eros nisl, sit amet interdum nunc ultrices vestibulum. Integer ac imperdiet purus, eu sodales massa. Donec in sollicitudin purus. Aenean at cursus nulla.
</p><p>
Integer id eleifend tortor. Maecenas id turpis vel sapien consequat blandit. Pellentesque at facilisis tellus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nullam tortor purus, sagittis ut faucibus sed, facilisis quis tortor. Aliquam tincidunt mi at ipsum congue varius.
</p>
</div>
I am using a classical website that is partially transparent and has a WebGL background, http://www.taccgl.org/. I had no real problems with various web browsers (testing myself on windows) and currently have no complaints, but of course IE<11 and various mobile browsers just don’t support WebGL. Problems are however with various devices that do not support WebGL at all or that do not provide the required performance for a full screen 3D canvas. So I found it necessary to monitor performance and to possibly disable WebGL or to resize the canvas. To keep the 3D canvas small it should cover only the visible part of the page, which means that upon scroll and resize the canvas position and size needs to be adapted. A good way in my opinion would be to use my open source library (when it is out of beta), but here I am biased of course.

Resources