I want to implement story items as different widgets. Like in this example:
In this picture, only images are changed, but I want to change as whole widgets as story items.
I have tried the story_view package. But, in this package, only images and videos can be added. Is there any other library for that?
As explained by https://stackoverflow.com/users/8164116/daksh-gargas, story view can be easily implemented using stack pageview and a simple gesture detector.
Made a simple story view -
import 'package:flutter/material.dart';
class CustomStoryView extends StatefulWidget{
#override
_CustomStoryViewState createState() => _CustomStoryViewState();
}
class _CustomStoryViewState extends State<CustomStoryView> with SingleTickerProviderStateMixin {
final List _colorsList = [Colors.blue, Colors.red, Colors.green, Colors.yellow, Colors.grey, Colors.brown];
final PageController _controller = PageController();
double _progressIndicators;
int _page = 0;
AnimationController _animationController;
bool dragEnded = true;
Size _pageSize;
#override
void initState() {
_animationController = AnimationController(vsync: this, duration: Duration(seconds: 2));
_animationController.addListener(animationListener);
_animationController.forward();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_pageSize = MediaQuery.of(context).size;
_progressIndicators = (_pageSize.width - 100) / 6;
});
super.initState();
}
#override
void dispose() {
_animationController?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
PageView.builder(
controller: _controller,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index)=>GestureDetector(
onLongPressStart: _onLongPressStart,
onLongPressEnd: _onLongPressEnd,
onHorizontalDragEnd: _onHorizontalDragEnd,
onHorizontalDragStart: _onHorizontalDragStart,
onHorizontalDragUpdate: _onHorizontalDragUpdate,
onTapUp: _onTapDown,
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: _colorsList[index],
child: Center(child: InkWell(
onTap: (){
print("thiswasclicked $index");
},
child: Text("Somee random text", style: TextStyle(fontSize: 36),)),),
),
),
itemCount: _colorsList.length,
),
Positioned(
top: 48,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: ([0,1,2,3,4,5].map((e) =>
(e == _page) ? Stack(
children: [
Container(
width: _progressIndicators,
height: 8 ,
color: Colors.black54,
),
AnimatedBuilder(
animation: _animationController,
builder: (ctx, widget){
return AnimatedContainer(
width: _progressIndicators * _animationController.value,
height: 8 ,
color: Colors.white,
duration: Duration(milliseconds: 100),
);
},
),
],
): Container(
width: _progressIndicators,
height: 8 ,
color: (_page >= e) ? Colors.white : Colors.black54,
)).toList()),
),)
],
),
);
}
animationListener(){
if(_animationController.value == 1){
_moveForward();
}
}
_moveBackward(){
if(_controller.page != 0){
setState(() {
_page = (_controller.page - 1).toInt();
_page = (_page < 0) ? 0 : _page;
_controller.animateToPage(_page, duration: Duration(milliseconds: 100), curve: Curves.easeIn);
_animationController.reset();
_animationController.forward();
});
}
}
_moveForward(){
if(_controller.page != (_colorsList.length - 1)){
setState(() {
_page = (_controller.page + 1).toInt();
_controller.animateToPage(_page, duration: Duration(milliseconds: 100), curve: Curves.easeIn);
_animationController.reset();
_animationController.forward();
});
}
}
_onTapDown(TapUpDetails details) {
var x = details.globalPosition.dx;
(x < _pageSize.width / 2) ? _moveBackward() : _moveForward();
}
_onHorizontalDragUpdate(d){
if (!dragEnded) {
dragEnded = true;
if (d.delta.dx < -5) {
_moveForward();
} else if (d.delta.dx > 5) {
_moveBackward();
}
}
}
_onHorizontalDragStart(d) {
dragEnded = false;
}
_onHorizontalDragEnd(d) {
dragEnded = true;
}
_onLongPressEnd(_){
_animationController.forward();
}
_onLongPressStart(_){
_animationController.stop();
}
}
This can be easily achieved with Stack, Container, and a GestureDetector to switch between pages/stories.
Why Stacks?
Flutter's Stack is useful if you want to overlap several
children in a simple way, for example, having some text and an image,
overlaid with a gradient and a button attached to the bottom.
To handle your "fixed" views, which are, in this case:
Top Progress bar... you can create your custom progress bar if you want.
That image and the user name...
Let's call them myTopFixedWidgets()
Row(children: [CircleAvatar(...),Column(children: [Text(...),Text(...)],)],)
Now, put your Widget that you want to display and that changes (your "story") as the first item of the Stacks and place the Widgets 1. and 2. (mentioned above) in the second item of the list.
Maintain a variable index to choose the widget that you want to display.
Stack(
children: <Widget>[
widgetsToShowAsAStory[index],
myTopFixedWidgets() //mentioned above
],
)
Wrap it inside GestureDetector
List<Widget> widgetsToShowAsAStory = [];
var index = 0;
....
GestureDetector(
onTap: () {
//If the tap is on the LEFT side of the screen then decrement the value of the index
index-= 1; //(check for negatives)
//If the tap is on the RIGHT side of the screen then increment the value of the index
index+= 1; //(check for the size of list)
//call
setState() {}
},
child: Stack(
children: <Widget>[
widgetsToShowAsAStory[index],
myTopFixedWidgets()
],
),)
and boom, you're good to go!
I found solutions from the story_view. But it doesnot match my requirement. We can only show different widgets as stories items in story_view.We can't perform any actions on widgets. To implement this story_view and to show different widgets as stories. Do like this.
First import story_view flutter dependencies from here.
Then import this in main.dart file.
import "package:story_view/story_view.dart";
StoryView(
controller: controller,
storyItems: [
StoryItem.inlineImage(
url:
"https://images.unsplash.com/photo-1536063211352-0b94219f6212?ixid=MXwxMjA3fDB8MHxzZWFyY2h8MXx8YmVhdXRpZnVsJTIwZ2lybHxlbnwwfHwwfA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60",
controller: controller,
),
StoryItem(
new Container(
margin: EdgeInsets.all(12),
child: StaggeredGridView.countBuilder(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 12,
itemCount: imageList.length,
itemBuilder: (context, index) {
return Container(
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.all(
Radius.circular(15))),
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(15)),
child: FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: imageList[index],
fit: BoxFit.cover,
),
),
);
},
staggeredTileBuilder: (index) {
return StaggeredTile.count(
1, index.isEven ? 1.2 : 1.8);
}),
),
duration: aLongWeekend,
shown: true),
StoryItem(
new Container(
margin: EdgeInsets.all(12),
child: StaggeredGridView.countBuilder(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 12,
itemCount: imageList.length,
itemBuilder: (context, index) {
return Container(
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.all(
Radius.circular(15))),
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(15)),
child: FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: imageList[index],
fit: BoxFit.cover,
),
),
);
},
staggeredTileBuilder: (index) {
return StaggeredTile.count(
1, index.isEven ? 1.2 : 1.8);
}),
),
duration: aLongWeekend,
shown: true),
],
onStoryShow: (s) {
print("Showing a story");
},
onComplete: () {
print("Completed a cycle");
},
progressPosition: ProgressPosition.top,
repeat: false,
inline: false,
),
Related
Hello everyone and thank you for taking the time to try to help me.
I use this library in flutter on mobile only => pub.dev dropdown_search
When I click on the dropdown, I load with an API call a list of users. I want to call them 20 by 20. The first call is done well, I have my first 20 users displayed and when I get to the bottom of my list I have a new API call that goes to display the next 20. This part also works.
My problem is that the list view in the dropdown doesn't refresh and doesn't show me my next 20 users and yet in the console I have my request that leaves with my 20 new users showing. If I close and reopen the dropdown the list is updated.
I contacted the developer who told me first to try to use this => myKey.currentState.reassemble() with my scrollController but it did not work.
He then explained that to solve my problem he could only see the use of a streambuilder. I tried it but it didn't work.
Would you have some hints to give me or something else please?
Here is a bit of what I did in code. In this example I not use StreamBuilder.
final GlobalObjectKey<DropdownSearchState<ListValueOptionList>> myKey = new GlobalObjectKey<DropdownSearchState<ListValueOptionList>>(50);
bool isListValueModify = false;
List<delegationModele.Delegation> listDelegations = [];
int indexDelegationList;
int pageCounter = 0;
ScrollController _scrollController = ScrollController();
List<ListValueOptionList> listValueOptionListModule;
List<ListValueOptionList> listValueOptionListTypeOfDelegation;
List<ListValueOptionList> listValueOptionsUserDelegation;
List userAlreadyUse = [];
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
print("Scroll event");
_loadValueOptionListSpecificPeople();
setState(() {});
myKey.currentState.reassemble();
}
});
}
#override
Widget build(BuildContext context) {
return Padding(
/// Allows resize when kerboard is open if
/// padding == MediaQuery.of(context).viewInsets
padding: padding,
child: Container(
decoration:BoxDecoration(
color: appStyleMode.primaryBackgroundColor,
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0)),
),
height: modalHeight,
child: Padding(
padding: const EdgeInsets.only(left:30, top:30.0, right: 30, bottom: 20),
child: ListView(
shrinkWrap: false,
children: [
getModificationItemListDelegationBuilder(),
],
),
),
),
);
}
SingleChildScrollView getModificationItemListDelegationBuilder(){
final appStyleMode = Provider.of<AppStyleModeNotifier>(context);
return SingleChildScrollView(
child: Column(
children: [
///Add specific people
Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.only(bottom: 10, top: 10),
child: Text(
"${getTranslated(context, "my_delegation_specific_user")} : ",
style: TextStyle(
color: specificPeopleisEmpty ? Colors.red : appStyleMode.blackWhiteColor,
fontSize: 16,
),
textAlign: TextAlign.start,
),
),
Container(
child: ListView.builder(
shrinkWrap: true,
itemCount: delegation.userDelegationRecipients.length ?? 0,
itemBuilder: (context, index){
return specificPeople(listValueOptionsUserDelegation, index);
},
),
),
],
),
);
}
Widget specificPeople(List<ListValueOptionList> listValueOptionList, int index){
final appStyleMode = Provider.of<AppStyleModeNotifier>(context);
delegationModele.UserDelegationRecipients userDelegation = delegation.userDelegationRecipients[index];
String optionUserDescription;
if(listValueOptionList != null){
listValueOptionList.removeWhere((element) => userLoad.id.stringOf == element.idValue);
for(ListValueOptionList valueOption in listValueOptionList){
if(valueOption.idValue == userDelegation.delegationUserId){
optionUserDescription = valueOption.listValueDescription;
}
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.6,
child: DropdownSearch<ListValueOptionList>(
key: myKey,
dropdownButtonProps: DropdownButtonProps(
color: appStyleMode.blackWhiteColor
),
popupProps: PopupProps.menu(
showSelectedItems: true,
menuProps: MenuProps(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
elevation: 5.0,
backgroundColor: appStyleMode.primaryBackgroundColor,
),
textStyle: TextStyle(
color: appStyleMode.blackWhiteColor
),
scrollbarProps: ScrollbarProps(
thumbVisibility: true,
thumbColor: appStyleMode.categorySelectorSelectedBackgroundColor,
interactive: true
),
listViewProps: ListViewProps(
controller: _scrollController,
shrinkWrap: true,
),
interceptCallBacks: true,
itemBuilder: (context, list, isSelected){
return new Container(
child: ListTile(
selected: isSelected,
title: Text(
list?.listValueDescription,
style: TextStyle(
color: appStyleMode.blackWhiteColor
),
),
),
);
},
loadingBuilder: (context, loadingText){
return Align(
alignment: Alignment.topCenter,
child: CircularProgressIndicator(),
);
},
emptyBuilder: (context, text){
return Align(
alignment: Alignment.topCenter,
child: Text(
"${getTranslated(context, "my_delegation_empty_search_user")}",
style: TextStyle(
color: appStyleMode.blackWhiteColor,
),
),
);
},
showSearchBox: true,
searchDelay: Duration(seconds: 1),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
enabled: true,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: appStyleMode.categorySelectorSelectedBackgroundColor,
style: BorderStyle.solid
)
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
style: BorderStyle.solid
)
),
prefixIcon: Icon(
Icons.search_rounded,
size: 20,
),
hintText: "${getTranslated(context, "my_delegation_search_hint_text")}",
hintStyle: TextStyle(
fontSize: 13
),
),
style: TextStyle(
color: appStyleMode.blackWhiteColor,
),
),
),
onChanged: (newValue){
//OnChanges is well used
},
compareFn: (value1, value2) => value1.idValue == value2.idValue,
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none
)
),
//items: listValueOptionsUserDelegation,
asyncItems: (filter) async{
return _loadValueOptionListSpecificPeople();
},
itemAsString: (ListValueOptionList list) => list.listValueDescription,
selectedItem: new ListValueOptionList(idValue: userDelegation.delegationUserId, listValueDescription: optionUserDescription),
dropdownBuilder: (context, selectedItem){
if(optionUserDescription == null || optionUserDescription.isEmpty){
return Container();
}else if(optionUserDescription == ""){
return Text("");
}else{
selectedItem.listValueDescription = optionUserDescription;
}
return textValueDescription(selectedItem.listValueDescription, appStyleMode, TextAlign.start);
},
),
),
],
);
}else{
return Container();
}
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}
I've had a similar app. Maybe this code will help:
class PostsList extends StatefulWidget {
#override
State<PostsList> createState() => _PostsListState();
}
class _PostsListState extends State<PostsList> {
final _scrollController = ScrollController();
#override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
#override
Widget build(BuildContext context) {
return BlocBuilder<PostBloc, PostState>(
builder: (context, state) {
switch (state.status) {
case PostStatus.failure:
return const Center(child: Text('failed to fetch posts'));
case PostStatus.success:
if (state.posts.isEmpty) {
return const Center(child: Text('no posts'));
}
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return index >= state.posts.length
? const BottomLoader()
: PostListItem(post: state.posts[index]);
},
itemCount: state.hasReachedMax
? state.posts.length
: state.posts.length + 1,
controller: _scrollController,
);
default:
return const Center(child: CircularProgressIndicator());
}
},
);
}
#override
void dispose() {
_scrollController
..removeListener(_onScroll)
..dispose();
super.dispose();
}
void _onScroll() {
if (_isBottom) context.read<PostBloc>().add(PostFetched());
}
bool get _isBottom {
if (!_scrollController.hasClients) return false;
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.offset;
return currentScroll >= (maxScroll * 0.9);
}
}
I've used bloc for this but it should work as well with future builder and using snapshot. Basically user scrolls down, and if scrolled more than 90% of the list another getter is send to get more items from api. If you want to replicate the whole bloc (which I strongly recommend) take look at this documentation.
I am making a quiz app and at first everything works fine, but when I do a quiz the first time, it does the correct or incorrect answer check perfectly.
But when I go back to quiz without restarting the app just navigating from one page to another the PageView does not reset its state again.
Before taking the quiz
enter image description here
After I do the quiz and I want to do it again without restart the app, I get the checked answers.
enter image description here
How to return the PageView to its initial state without restart the app
Here is my code:
import 'package:flutter/material.dart';
import 'package:quizapp/src/models/quiz_model.dart';
import 'package:quizapp/src/screens/result_screen.dart';
class QuizScreen extends StatefulWidget {
const QuizScreen({Key? key}) : super(key: key);
#override
State<QuizScreen> createState() => _QuizScreenState();
}
class _QuizScreenState extends State<QuizScreen> {
int _questionNumber = 1;
late PageController _controller;
int _score = 0;
#override
void initState() {
_controller = PageController(initialPage: 0);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: PageView.builder(
physics: const NeverScrollableScrollPhysics(),
controller: _controller,
itemCount: questions.length,
itemBuilder: (context, index) {
final _question = questions[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 16,
),
Text(
_question.text,
style: const TextStyle(fontSize: 22),
),
const SizedBox(
height: 16,
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: _question.options
.map((option) => GestureDetector(
onTap: () {
Future.delayed(
const Duration(milliseconds: 250),
() {
if (_questionNumber <
questions.length) {
_controller.nextPage(
duration: const Duration(
milliseconds: 250),
curve: Curves.easeInExpo);
setState(() {
if (option.isCorrect == true) {
_score++;
}
});
setState(() {
_questionNumber++;
// _isLocked = false;
});
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
ResultScreen(
score: _score),
));
}
});
if (_question.isLocked) {
return;
} else {
setState(() {
_question.isLocked = true;
_question.selectedOption = option;
});
}
},
child: Container(
height: 50,
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.symmetric(
vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFF6949FD),
borderRadius:
BorderRadius.circular(16),
border: Border.all(
color: getColorForOption(
option, _question))),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
option.text,
style: const TextStyle(
fontSize: 18,
color: Colors.white),
),
const SizedBox(width: 10),
getIconForOption(option, _question)
],
),
),
))
.toList(),
)))
]);
},
)),
),
],
),
));
}
Color getColorForOption(Option option, Question _question) {
final isSelected = option == _question.selectedOption;
if (_question.isLocked) {
if (isSelected) {
return option.isCorrect ? Colors.green : Colors.red;
} else if (option.isCorrect) {
return Colors.green;
}
}
return const Color(0xFF6949FD);
}
Widget getIconForOption(Option option, Question _question) {
final isSelected = option == _question.selectedOption;
if (_question.isLocked) {
if (isSelected) {
return option.isCorrect
? const Icon(Icons.check_circle, color: Colors.green)
: const Icon(Icons.cancel, color: Colors.red);
} else if (option.isCorrect) {
return const Icon(Icons.check_circle, color: Colors.green);
}
}
return const SizedBox.shrink();
}
}
An easier way is to restart the app when you go back or press a button. You can wrap Scaffold() with WillPopScope() to restart when you back. You can use this package to restart.
If you need to save the score, you can save it in local storage. Another easy package for this is get_storage.
dependencies:
flutter_phoenix: ^1.1.0
runApp(Phoenix(child: const MyApp()));
WillPopScope(
onWillPop: () async {
Phoenix.rebirth(context);
},
child: Scaffold())
I'm trying to select multiple images so for this i used pickMultiImage method of image_picker.
Images are displaying on screen, but i need their path so that i can use it to upload on cloudinary.com.
here is my code
List<XFile>? _imageFileList3 = [];
Future pickMultipleImage() async {
if (_imageFileList3!.length == 4) {
showDialog(
context: context,
builder: (BuildContext context) {
return LoginSucessDailog(
text: 'You can\'t add more than 4 images.',
title: 'Warning.',
img: 'assets/img/alert.png');
});
} else {
try {
var image = await _picker.pickMultiImage();
//here i'll be using cloudinary code
setState(() {
_imageFileList3!.addAll(image!);
});
print(image);
print(_imageFileList3!.length);
setState(() {
isImageLoading = false;
});
} on CloudinaryException catch (e) {}
}
}
this is the part of code i'm using to upload images on Cloudinary using cloudinary_public package
CloudinaryResponse response = await cloudinary.uploadFile(
CloudinaryFile.fromFile(image!.path,
resourceType: CloudinaryResourceType.Image),
);
displaying images like this
addProductsImages() {
if (_imageFileList3!.length != 0) {
return SizedBox(
height: 80,
width: MediaQuery.of(context).size.width * 0.9,
child: GridView.builder(
shrinkWrap: true,
itemCount: _imageFileList3!.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
),
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(children: [
ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Image.file(
File((_imageFileList3![index].path)),
width: MediaQuery.of(context).size.width * 0.35,
height: MediaQuery.of(context).size.height * 0.17,
fit: BoxFit.cover,
),
),
Align(
alignment: Alignment.topRight,
child: buildCancelIcon(Colors.white, () {
setState(() {
// _imageFileList!.removeAt(index);
});
}, Icons.cancel))
]));
}));
} else {
return Padding(
padding: const EdgeInsets.only(left: 70),
child:
Row(crossAxisAlignment: CrossAxisAlignment.center, children: []));
}
}
please help how to do this, or is there any way to select multiple images at once and upload them on cloudinary.
Please refer to below example code where user can pick maximum 5 images
Using these packages
images_picker: ^1.2.4
flutter_image_compress: ^0.7.0
class PickMultipleImagesScreen extends StatefulWidget {
const PickMultipleImagesScreen({Key key}) : super(key: key);
#override
_PickMultipleImagesScreenState createState() =>
_PickMultipleImagesScreenState();
}
class _PickMultipleImagesScreenState extends State<PickMultipleImagesScreen> {
final ValueNotifier<bool> attachMultipleImages = ValueNotifier<bool>(false);
List compressedPhotosList = ["place_holder"];
int maxImagesCount = 5;
pickPhotos() async {
List<Media> photosList = [];
photosList = await ImagesPicker.pick(
count: (compressedPhotosList != null &&
(compressedPhotosList.isNotEmpty) &&
(compressedPhotosList.length > 1))
? (maxImagesCount + 1 - compressedPhotosList.length)
: maxImagesCount,
pickType: PickType.all,
language: Language.System,
cropOpt: CropOption(
aspectRatio: CropAspectRatio(600, 400),
),
);
if (photosList != null && photosList.isNotEmpty && photosList.length > 0) {
for (int i = 0; i < photosList.length; i++) {
File photoCompressedFile =
await compressImage(File(photosList[i].path));
print("Images List: $photosList");
print("Path of UnCompressed File: ${photosList[i].path}");
compressedPhotosList.insert(
0,
photoCompressedFile.path.toString(),
);
print("Path of Compressed File: ${photoCompressedFile.path}");
print("Compressed Images List: $compressedPhotosList");
}
attachMultipleImages.value = !attachMultipleImages.value;
}
}
Future<File> compressImage(File file) async {
final filePath = file.absolute.path;
final lastIndex = filePath.lastIndexOf(new RegExp(r'.png|.jp'));
final splitted = filePath.substring(0, (lastIndex));
final outPath = "${splitted}_out${filePath.substring(lastIndex)}";
if (lastIndex == filePath.lastIndexOf(new RegExp(r'.png'))) {
final compressedImage = await FlutterImageCompress.compressAndGetFile(
filePath, outPath,
minWidth: 1000,
minHeight: 1000,
quality: 50,
format: CompressFormat.png);
return compressedImage;
} else {
final compressedImage = await FlutterImageCompress.compressAndGetFile(
filePath,
outPath,
minWidth: 1000,
minHeight: 1000,
quality: 50,
);
return compressedImage;
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: ValueListenableBuilder<bool>(
valueListenable: attachMultipleImages,
builder: (context, snapshot, child) {
return Scaffold(
body: (compressedPhotosList != null &&
compressedPhotosList.isNotEmpty &&
compressedPhotosList.length > 1)
? GridView.builder(
itemCount: (compressedPhotosList != null &&
compressedPhotosList.isNotEmpty &&
compressedPhotosList.length > 1 &&
(compressedPhotosList.length - 1 == maxImagesCount))
? compressedPhotosList.length - 1
: compressedPhotosList.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 4.0,
mainAxisSpacing: 4.0),
itemBuilder: (BuildContext context, int index) {
return ((compressedPhotosList[index] == "place_holder") &&
compressedPhotosList.length - 1 != maxImagesCount)
? InkWell(
onTap: () async {
if (compressedPhotosList.length - 1 !=
maxImagesCount) {
pickPhotos();
}
},
child: Container(
margin: EdgeInsets.all(
5.0,
),
width: ScreenUtil().screenWidth,
height: ScreenUtil().setHeight(105.0),
color: Colors.blueAccent,
child: Center(
child: Icon(
Icons.add,
size: ScreenUtil().setSp(24.0),
color: Colors.grey,
),
),
),
)
: Stack(
clipBehavior: Clip.none,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4.0),
child: Image.file(
File(compressedPhotosList[index]),
fit: BoxFit.fitHeight,
width: ScreenUtil().screenWidth,
height: ScreenUtil().setHeight(105.0),
filterQuality: FilterQuality.low,
errorBuilder: (context, error, stackTrace) {
return Container(
width: ScreenUtil().screenWidth,
height: ScreenUtil().setHeight(105.0),
color: Colors.black,
);
},
),
),
Positioned(
bottom: 10,
right: 8,
child: InkWell(
onTap: () async {
compressedPhotosList.removeAt(index);
attachMultipleImages.value =
!attachMultipleImages.value;
},
child: CircleAvatar(
radius: 15.0,
backgroundColor: Colors.black45,
child: Icon(
Icons.delete_forever,
color: Colors.white,
size: 20,
),
),
),
)
],
);
},
)
: Center(
child: InkWell(
onTap: () {
pickPhotos();
},
child: Text("Attach Images"),
),
),
);
}
),
);
}
}
I tried to build an application, which shows the user some places on a google maps. The data for the places (location, name) should the app take from the firestore. The app already can display the maker in the google maps, but I also want some details in the bottom of the application in an Animated Builder, but it doesn't work. The problem is, that I can't get the name of the place with specify['name'] in my _restaurantList function, but in the initMarker function it works. I think it has to do something with the _restaurantList(specify) and the return _restaurantList(index) but I don't know what the mistake is. I also tried to use specify instead of index in the _restaurantList(index), but then I got an error. But I think I have to use _restaurantList(specify) in order to use specify['name'] afterwards.
Does anyone know what my mistake is?
That's my code:
void initMarker(specify, specifyId) async {
// await Firebase.initializeApp();
var markerIdVal = specifyId;
final MarkerId markerId = MarkerId(markerIdVal);
final Marker marker = Marker(
markerId: markerId,
position:
LatLng(specify['location'].latitude, specify['location'].longitude),
infoWindow: InfoWindow(title: specify['name'], snippet: 'Shop'),
);
print(specify['location'].latitude);
nameTest = specify['name'];
setState(() {
markers[markerId] = marker;
print(markerId.toString() + '__________________________');
});
}
getMarkerData() async {
Firestore.instance.collection('seller').getDocuments().then((myMockDoc) {
if (myMockDoc.documents.isNotEmpty) {
for (int i = 0; i < myMockDoc.documents.length; i++) {
length = myMockDoc.documents.length;
print(length);
initMarker(
myMockDoc.documents[i].data(), myMockDoc.documents[i].documentID);
}
}
});
}
_restaurantList(specify) {
return AnimatedBuilder(
animation: _pageController,
builder: (BuildContext context, Widget widget) {
double value = 1;
if (_pageController.position.haveDimensions) {
//value = _pageController.page - index;
value = (1 - (value.abs() * 0.3) + 0.06).clamp(0.0, 1.0);
}
return Center(
child: SizedBox(
height: Curves.easeInOut.transform(value) * 175.0,
width: Curves.easeInOut.transform(value) * 350.0,
child: widget,
),
);
},
child: InkWell(
onTap: () {
null;
},
child: Stack(
children: [
Center(
child: Container(
margin: EdgeInsets.symmetric(
horizontal: 10.0,
vertical: 20.0,
),
height: 125.0,
width: 275.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
boxShadow: [
BoxShadow(
color: Colors.black54,
offset: Offset(0.0, 4.0),
blurRadius: 10.0,
),
]),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: Colors.white),
child: Text(
specify['name'],
),
),
),
)
],
),
),
);
}
And this is in the Scaffold:
Positioned(
bottom: 20.0,
child: Container(
height: 200.0,
width: MediaQuery.of(context).size.width,
child: PageView.builder(
controller: _pageController,
itemCount: length,
itemBuilder: (BuildContext context, int index) {
return _restaurantList(index);
},
),
),
)
I have the following streambuilder below. If I put the GestureDetector on the Row widget (as indicated below) it receives the gesture. However, when I put it as shown, it does not. My current theory is that it is due to the List.generation there, however, I guess it could be because there are other widgets above it? It's in a Stack widget...although, if that's the case, why would the GestureDetector work on the Row widget?)
return StreamBuilder<List<List<Event>>>(
stream: widget.controller.stream.map(_filter),
initialData: Provider.of<CalendarData>(context).dayEvents,
builder: (context, snapshot) {
return Row(
//GESTUREDETECTOR WORKS HERE
children: List.generate(8, (col) {
if (col == 0) {
return Expanded(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
print('tapped: beer'); //<-- col
},
onScaleStart: (scaleDetails) => setState(() {
print('previousNumOfDays:$previousNumOfDays');
print('numberOfDays:$numberOfDays');
// dayIndexScaleCenter = col;
print('dayIndexScaleCenter: $dayIndexScaleCenter');
previousNumOfDays = numberOfDays;
}),
onScaleUpdate: (ScaleUpdateDetails scaleDetails) {
setState(() {
int newNumberOfDays =
(previousNumOfDays / scaleDetails.scale).round();
print('previousNumOfDays:$previousNumOfDays');
print('numberOfDays:$numberOfDays');
print('newNumberOfDays:$newNumberOfDays');
if (newNumberOfDays <= 14 && newNumberOfDays > 1) {
numberOfDays = newNumberOfDays;
}
});
},
child: Column(
children: List.generate(
hours.length,
(row) => Container(
height: Provider.of<CalendarData>(context).rowHeight,
decoration: BoxDecoration(
color: ColorDefs.colorTimeBackground,
border: Border(
top: BorderSide(
width: 1.0,
color: ColorDefs.colorCalendarHeader),
),
),
child: Center(
child: AutoSizeText(hours[row],
maxLines: 1,
group: timeAutoGroup,
minFontSize: 5,
style: ColorDefs.textSubtitle2),
),
),
),
),
),
);
}