I am building a chatbot with text and button responses. I want to hide the button container when one of the buttons is pressed.These buttons are dynamic. I am trying to use a boolean array to save the visibility of the list of the elements. Now, the buttons disappear whenever i press them, but they appear again when the next response is loaded.
class Body extends StatefulWidget {
final List<Map<String, dynamic>> messages;
final mycallback? hi;
final callback? showdate;
Body({
Key? key,
this.messages = const [],
this.hi,
this.showdate,
}) : super(key: key);
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
List<bool> demo = List<bool>.filled(50, true);
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (context, i) {
var obj = widget.messages[widget.messages.length - 1 - i];
Message message = obj['message'];
bool isUserMessage = obj['isUserMessage'] ?? false;
List<dynamic> label = obj['label'];
bool show = obj['show'];
//if (show) show1();
return Column(
crossAxisAlignment:
isUserMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: isUserMessage
? MainAxisAlignment.end
: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_MessageContainer(
message: message,
isUserMessage: isUserMessage,
),
//if (label.length > 0) _ButtonContainer(label: label),
if (label.length > 0)
if (label[0] != "calendar")
!demo[i]
? SizedBox(
width: 2,
)
: Visibility(
child: Container(
constraints: BoxConstraints(maxWidth: 250),
padding: EdgeInsets.only(top: 10, left: 35),
child: ListView.builder(
itemCount: label.length,
shrinkWrap: true,
itemBuilder: (context, j) {
return ElevatedButton(
child: Text(label[j]),
onPressed: () => {
print(i),
setState(() {
hide(i + 1);//hiding here
print(i);
widget.hi!(label[j], true);//sending message
})
},
style: ButtonStyle(
shape: MaterialStateProperty.all<
RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
side: BorderSide(
color: Colors.black,
width: 2.0,
),
),
),
),
);
},
),
),
)
],
);
},
separatorBuilder: (_, i) => Container(height: 10),
itemCount: widget.messages.length,
reverse: true,
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 20,
),
);
}
void hide(i) {
print("I am inside hide");
print(demo[i]);
inspect(demo);
setState(() {
demo[i] = false;
if (demo[i]) {
debugPrint("true");
} else {
debugPrint("False");
}
});
inspect(demo);
}
}
Related
I'm trying to setup a Flutter parent/child widget configuration to allow for multiple textControllers on the child widget and the parent widget to control the saving of the form answer and some validation.
I need to be able to have multiple answer areas. Most questions will have 1 answer area but some will have multiple.
So far, I have a function being passed into the Child Widget called nextPageStatus. This is used in the textController Listener to send a true/false back to the Parent Widget and set the active status of the "Next" question button.
What I can't figure out is how to get the values from the textController in the Parent Widget. I need those values on the Parent Widget because I call the database to save the answers on the Parent Widget.
Parent Wiget:
class ParentWidget extends StatefulWidget {
ParentWidget({super.key});
#override
State<StatefulWidget> createState() => ParentWidgetState();
}
class ParentWidgetState extends State<ParentWidget> {
late int _currentIndex = 0;
bool nextPageIsActive = false;
bool prevPageIsActive = false;
late SwiperController _swiperController;
List<QuestionModel> questionList = [];
List<AnswerModel> answerList = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children:[
Row(
children: [
Column(
children: [
IconButton(
onPressed: prevPageIsActive ? _previousCard : () {},
icon: Icon(
Icons.arrow_back,
color: prevPageIsActive ? null : Colors.grey,
)
),
const Text("Back")
]),
Column(
children: [
IconButton(
onPressed: nextPageIsActive ? _nextPageAction : () { },
icon: Icon(
Icons.arrow_forward,
color: nextPageIsActive ? null : Colors.grey
)
),
Text("Next")
]
),
],
),
]
)
);
}
void setNextPageStatus(bool status) {
setState(() {
nextPageIsActive = status;
});
}
void _previousCard() {
_swiperController.previous(animation: true);
setState(() {
prevPageIsActive = false;
})
}
void _nextCard() {
_swiperController.next(animation: true);
setState(() {
prevPageIsActive = true;
});
}
void _saveAnswerToDatabase() {
final firestoreDatabase =
Provider.of<FirestoreDatabase>(context, listen: false);
List<String> answers = [];
// TODO: Get all answers from Child Widget here
// for (var ans in answerList) {
// answers.add(ans);
// }
firestoreDatabase.setUserAnswers(answers, userId);
}
void _nextPageAction() {
// this function should only be available
// once requirements are met on child
// (must have at least 7 letters typed for question)
_saveAnswerToDatabase();
if (questionList.length == (_currentIndex + 1)) {
// last prompt -- finish
Navigator.pushNamed(
context,
AppRoutes.home,
);
} else {
_nextCard();
}
}
Widget buildSwiper() {
return Expanded(
child: Swiper(
controller: _swiperController,
itemCount: questionList.length,
onIndexChanged: (index) {
setState(() {
_currentIndex = index;
});
},
itemBuilder: Card(
child: SingleChildScrollView(
child: Column(
children: [
// !! this is where I iterate over the all the possible questions
for (int i = 0; i < currentPrompt.questionPrompts.length; i++)
AnswerArea(
key: widget.promptScreenKey,
nextPageStatus: setNextPageStatus,
questionPrompt: questionList[index].questionPrompts[i],)
],
),
),
),
),
);
}
}
Child Widget (Answer Area):
class AnswerArea extends StatefulWidget {
AnswerArea({Key? key, required this.textPrompt, required this.nextPageStatus}) : super(key: key);
late String textPrompt;
late Function(bool) nextPageStatus;
#override
State<AnswerArea> createState() => _AnswerAreaState();
}
class _AnswerAreaState extends State<AnswerArea> {
late List<TextEditingController> answerAreaTextControllers;
final answerAreaTextController = TextEditingController();
_textAnswerListener() {
if (answerAreaTextController.text.isNotEmpty && answerAreaTextController.text.length >= 7) {
widget.nextPageStatus(true);
} else {
widget.nextPageStatus(false);
}
}
#override
Widget build(BuildContext context) {
answerAreaTextController.addListener(_textAnswerListener);
const maxLines = 5;
const numberOfLines = 5;
const cursorHeight = 22.0;
return Container(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
child: Column(
children: [
Stack(
children: [
Container(
child: SizedBox(
height: numberOfLines * (cursorHeight + 8),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: TextField(
controller: answerAreaTextController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: widget.textPrompt,
hintStyle: TextStyle(color: Colors.white),
),
cursorHeight: cursorHeight,
keyboardType: TextInputType.multiline,
maxLines: maxLines,
),
),
),
),
for (int i = 0; i < numberOfLines; i++)
answerAreaTextController.text.isEmpty ? Container(
width: double.infinity,
margin: EdgeInsets.only(
top: 4 + (i + 1) * cursorHeight,
left: 15,
right: 15,
),
height: 1,
color: Colors.white,
) : Container(),
],
),
],
),
);
}
}
I tried to fetch data from firestore to chip widgets but then show "LateInitializationError". And also chips should be can multi selection(select many chips). And also how to align 4 chips in a row like this example?I my code I think chips are show like ListView.
error..
I mean like this..
my code..
class uitry extends StatefulWidget {
const uitry({Key? key}) : super(key: key);
#override
State<uitry> createState() => _uitryState();
}
#override
Future<List<Words12>> fetchRecords() async {
var records = await FirebaseFirestore.instance.collection('12words').get();
return mapRecords(records);
}
List<Words12> mapRecords(QuerySnapshot<Map<String, dynamic>> records) {
var _list = records.docs
.map(
(words12) => Words12(
id: words12.id,
wordName: words12['wordName'],
categoryName: words12['categoryName'],
),
)
.toList();
return _list;
}
late int defaultChoiceIndex;
#override
void initState() {
initState();
defaultChoiceIndex = 0;
}
child: SizedBox(
width: width * 0.94,
height: height * 0.30,
child: FutureBuilder<List<Words12>>(
future: fetchRecords(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List<Words12> data = snapshot.data ?? [];
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return (ChoiceChip(
label: Text(data[index].wordName),
selected: defaultChoiceIndex == index,
selectedColor: Colors.deepPurple,
onSelected: (value) {
setState(() {
defaultChoiceIndex =
value ? index : defaultChoiceIndex;
});
},
// backgroundColor: color,
elevation: 1,
padding: const EdgeInsets.symmetric(
horizontal: 5.0),
));
},
);
}
}),
),
#override
void initState() {
initState();
defaultChoiceIndex = 0;
}
Should be:
#override
void initState() {
super.initState();
defaultChoiceIndex = 0;
}
I believe it will initialize your defaultChoiceIndex then.
For the alignment of chips: Wrap your ChoiceChip in the ListView.builder in a Row(), with a mainAxisAlignment of your choosing:
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ChoiceChip(etc.),
],
),
I tried it .Now it working..
code
SizedBox(
width: width * 0.94,
height: height * 0.30,
child: Column(
children: <Widget>[
const SizedBox(height: 16),
Wrap(
children: hobbyList.map(
(hobby) {
bool isSelected = false;
if (selectedHobby!.contains(hobby)) {
isSelected = true;
}
return GestureDetector(
onTap: () {
if (!selectedHobby!.contains(hobby)) {
if (selectedHobby!.length < 50) {
selectedHobby!.add(hobby);
setState(() {});
print(selectedHobby);
}
} else {
selectedHobby!.removeWhere(
(element) => element == hobby);
setState(() {});
print(selectedHobby);
}
},
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 5, vertical: 4),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 5, horizontal: 12),
decoration: BoxDecoration(
color: isSelected
? HexColor('#F5F185')
: HexColor('#D9D9D9'),
borderRadius:
BorderRadius.circular(18),
border: Border.all(
color: isSelected
? HexColor('#F5F185')
: HexColor('#D9D9D9'),
width: 2)),
child: Text(
hobby,
style: TextStyle(
color: isSelected
? Colors.black
: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w600),
),
),
),
);
},
).toList(),
),
],
),
),
class _uitryState extends State<uitry> {
List<String> hobbyList = [
'Shopping',
'Brunch',
'Music',
'Road Trips',
'Tea',
'Trivia',
'Comedy',
'Clubbing',
'Drinking',
'Wine',
];
List<String>? selectedHobby = [];
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 try to create horizontal listview with 2 row and many column (may horizontal scrollview) like above image. if in android I can use listview with gridlayoutmanager. how to I do in flutter?
and I want to my menu can reorderable, so I use ReorderableList, this my fullcode
import 'package:flutter/material.dart' hide ReorderableList;
import 'package:flutter_app2/home.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter_reorderable_list/flutter_reorderable_list.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class ItemData {
ItemData(this.title, this.key);
final String title;
// Each item in reorderable list needs stable and unique key
final Key key;
}
enum DraggingMode {
iOS,
Android,
}
class Item extends StatelessWidget {
Item({
required this.data,
required this.isFirst,
required this.isLast,
required this.draggingMode,
});
final ItemData data;
final bool isFirst;
final bool isLast;
final DraggingMode draggingMode;
Widget _buildChild(BuildContext context, ReorderableItemState state) {
BoxDecoration decoration;
if (state == ReorderableItemState.dragProxy ||
state == ReorderableItemState.dragProxyFinished) {
// slightly transparent background white dragging (just like on iOS)
decoration = BoxDecoration(color: Color(0xD0E81818));
} else {
bool placeholder = state == ReorderableItemState.placeholder;
decoration = BoxDecoration(
border: Border(
top: isFirst && !placeholder
? Divider.createBorderSide(context) //
: BorderSide.none,
bottom: isLast && placeholder
? BorderSide.none //
: Divider.createBorderSide(context)),
color: placeholder ? null : Colors.blue);
}
// For iOS dragging mode, there will be drag handle on the right that triggers
// reordering; For android mode it will be just an empty container
Widget dragHandle = draggingMode == DraggingMode.iOS
? ReorderableListener(
child: Container(
padding: EdgeInsets.only(right: 18.0, left: 18.0),
color: Color(0x08000000),
child: Center(
child: Icon(Icons.reorder, color: Color(0xFF6AA848)),
),
),
)
: Container();
Widget content = Container(
decoration: decoration,
child: SafeArea(
top: false,
bottom: false,
child: Opacity(
// hide content for placeholder
opacity: state == ReorderableItemState.placeholder ? 0.0 : 1.0,
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Padding(
padding:
EdgeInsets.symmetric(vertical: 14.0, horizontal: 14.0),
child: Text(data.title,
style: Theme.of(context).textTheme.subtitle1),
)),
// Triggers the reordering
dragHandle,
],
),
),
)),
);
// For android dragging mode, wrap the entire content in DelayedReorderableListener
if (draggingMode == DraggingMode.Android) {
content = DelayedReorderableListener(
child: content,
);
}
return content;
}
#override
Widget build(BuildContext context) {
return ReorderableItem(
key: data.key, //
childBuilder: _buildChild);
}
}
class _MyHomePageState extends State<MyHomePage> {
late List<ItemData> _items;
_MyHomePageState() {
_items = [];
for (int i = 0; i < 500; ++i) {
String label = "List item $i";
if (i == 5) {
label += ". This item has a long label and will be wrapped.";
}
_items.add(ItemData(label, ValueKey(i)));
}
}
// Returns index of item with given key
int _indexOfKey(Key key) {
return _items.indexWhere((ItemData d) => d.key == key);
}
bool _reorderCallback(Key item, Key newPosition) {
int draggingIndex = _indexOfKey(item);
int newPositionIndex = _indexOfKey(newPosition);
final draggedItem = _items[draggingIndex];
setState(() {
debugPrint("Reordering $item -> $newPosition");
_items.removeAt(draggingIndex);
_items.insert(newPositionIndex, draggedItem);
});
return true;
}
void _reorderDone(Key item) {
final draggedItem = _items[_indexOfKey(item)];
debugPrint("Reordering finished for ${draggedItem.title}}");
}
DraggingMode _draggingMode = DraggingMode.iOS;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ReorderableList(
onReorder: this._reorderCallback,
onReorderDone: this._reorderDone,
child: CustomScrollView(
// cacheExtent: 3000,
slivers: <Widget>[
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Item(
data: _items[index],
// first and last attributes affect border drawn during dragging
isFirst: index == 0,
isLast: index == _items.length - 1,
draggingMode: _draggingMode,
);
},
childCount: _items.length,
),
)),
],
),
),
);
}
}
and this my current view
what about using reorderables, ReorderableWrap:
class _NestedWrapExampleState extends State<NestedWrapExample> {
// List<Widget> _tiles;
Color _color;
Color _colorBrighter;
#override
void initState() {
super.initState();
_color = widget.color ?? Colors.primaries[widget.depth % Colors.primaries.length];
_colorBrighter = Color.lerp(_color, Colors.white, 0.6);
}
#override
Widget build(BuildContext context) {
void _onReorder(int oldIndex, int newIndex) {
setState(() {
widget._tiles.insert(newIndex, widget._tiles.removeAt(oldIndex));
});
}
var wrap = ReorderableWrap(
spacing: 8.0,
runSpacing: 4.0,
padding: const EdgeInsets.all(8),
children: widget._tiles,
onReorder: _onReorder
);
var buttonBar = Container(
color: _colorBrighter,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
IconButton(
iconSize: 42,
icon: Icon(Icons.add_circle),
color: Colors.deepOrange,
padding: const EdgeInsets.all(0.0),
onPressed: () {
setState(() {
widget._tiles.add(
Card(
child: Container(
child: Text('${widget.valuePrefix}${widget._tiles.length}', textScaleFactor: 3 / math.sqrt(widget.depth + 1)),
padding: EdgeInsets.all((24.0 / math.sqrt(widget.depth + 1)).roundToDouble()),
),
color: _colorBrighter,
elevation: 3,
)
);
});
},
),
IconButton(
iconSize: 42,
icon: Icon(Icons.remove_circle),
color: Colors.teal,
padding: const EdgeInsets.all(0.0),
onPressed: () {
setState(() {
widget._tiles.removeAt(0);
});
},
),
IconButton(
iconSize: 42,
icon: Icon(Icons.add_to_photos),
color: Colors.pink,
padding: const EdgeInsets.all(0.0),
onPressed: () {
setState(() {
widget._tiles.add(NestedWrapExample(depth: widget.depth + 1, valuePrefix: '${widget.valuePrefix}${widget._tiles.length}.',));
});
},
),
Text('Level ${widget.depth} / ${widget.valuePrefix}'),
],
)
);
var column = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buttonBar,
wrap,
]
);
return SingleChildScrollView(
child: Container(child: column, color: _color,),
);
}
}
I want to have all the dates of a month in a horizontal scroll view. The current week 7 days should be displayed first and on scrolling right the previous dates should be shown. later on scrolling left the later weeks dates should be displayed an don tap of a date i should get the date in return. How to do this? I have tried using the below. It displays dates and scrolls horizontally as well but it displays only multiple of 7 and all the exact dates of a month. Also on tapping the date it does not return the position form listview builder as 0 and i returns the index.
Widget displaydates(int week) {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int position) {
return Row(
children: <Widget>[
for (int i = 1; i < 8; i++)
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
print(position);
},
child: Text(
((week * 7) + i).toString() + " ",
style: TextStyle(),
),
),
),
],
);
});
}
I am calling this like:
displaydates(0),
displaydates(1),
displaydates(2),
displaydates(3),
displaydates(4),
UPDATE:
You can get last day of month using below code :
DateTime(year,month + 1).subtract(Duration(days: 1)).day;
You should use ModalBottomSheet for the same and then pop that on selection with the Navigator.of(context).pop(result);
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int position) {
return Row(
children: <Widget>[
for (int i = 1; i < 8; i++)
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(position);
},
child: Text(
((week * 7) + i).toString() + " ",
style: TextStyle(),
),
),
),
],
);
}
);
}
);
I have created a widget that let me select date from a list and it is scrollable(along time ago). There lot of code but you can use the selected date under any widget which parent wrapped from InheritedWidget.
Here is the code(Note that I also created a package for this, if you dont like to write this much code for this):
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class MyInheritedWidget extends InheritedWidget {
final DateTime date;
final int selectedDay;
final int monthDateCount;
final bool isDateHolderActive;
final Map<int, bool> dayAvailabilityMap;
final ValueChanged<bool> toggleDateHolderActive;
final ValueChanged<int> setSelectedDay;
MyInheritedWidget({
Key key,
this.date,
this.selectedDay,
this.monthDateCount,
this.isDateHolderActive,
this.dayAvailabilityMap,
this.toggleDateHolderActive,
this.setSelectedDay,
Widget child,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return oldWidget.selectedDay != selectedDay ||
oldWidget.toggleDateHolderActive != toggleDateHolderActive;
}
}
class DateIndicatorPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
DateIndicator(),
Expanded(
child: Container(),
),
],
),
),
);
}
}
class DateIndicator extends StatefulWidget {
static MyInheritedWidget of(BuildContext context) => context.dependOnInheritedWidgetOfExactType();
#override
_DateIndicatorState createState() => _DateIndicatorState();
}
class _DateIndicatorState extends State<DateIndicator> {
DateTime date = DateTime.now();
int selectedDay = 1;
int monthDateCount = 1;
bool isDateHolderActive = false;
Map<int, bool> dayAvailabilityMap = {};
void toggleDateHolderActive(bool flag) {
setState(() {
isDateHolderActive = flag;
});
}
void setSelectedDay(int index) {
setState(() {
selectedDay = index;
});
}
#override
void initState() {
final DateTime dateForValues = new DateTime(date.year, date.month + 1, 0);
monthDateCount = dateForValues.day;
// Just to show how to activate when something exist for this day(from network response or something)
dayAvailabilityMap[1] = true;
dayAvailabilityMap[2] = true;
dayAvailabilityMap[3] = true;
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: 68.0,
padding:
const EdgeInsets.only(left: 7.0, right: 3.0, top: 2.0, bottom: 2.0),
decoration: BoxDecoration(
color: Theme.of(context).secondaryHeaderColor,
boxShadow: [
BoxShadow(
color: Colors.blueAccent.withOpacity(.7),
offset: Offset(0.0, .5),
blurRadius: 3.0,
spreadRadius: 0.3),
],
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: monthDateCount, // to avoid showing zero
itemBuilder: (BuildContext context, int index) {
return MyInheritedWidget(
date: date,
selectedDay: selectedDay,
monthDateCount: monthDateCount,
isDateHolderActive: isDateHolderActive,
dayAvailabilityMap: dayAvailabilityMap,
toggleDateHolderActive: toggleDateHolderActive,
setSelectedDay: setSelectedDay,
child: DateHolder(index));
}),
);
}
}
class DateHolder extends StatelessWidget {
DateHolder(this.index);
final int index;
final Widget activeBubble = Container(
width: 15.0,
height: 15.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.deepOrangeAccent,
),
);
#override
Widget build(BuildContext context) {
final appState = DateIndicator.of(context);
return InkWell(
onTap: () {
appState.toggleDateHolderActive(true);
appState.setSelectedDay(index);
print("Date ${index} selected!");
},
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
Container(
margin: const EdgeInsets.only(right: 5.0),
child: Text(
"${DateFormat('EEEE').format(DateTime(appState.date.year, appState.date.month, index)).substring(0, 1)}",
style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12.0),
)),
Container(
width: 45.0,
height: 45.0,
margin: const EdgeInsets.only(right: 5.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
border: (index == (appState.selectedDay) &&
appState.isDateHolderActive == true)
? Border.all(width: 2.0, color: Theme.of(context).primaryColor)
: Border.all(color: Colors.transparent),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
"${index + 1}", // to avoid showing zero
style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 16.0),
),
),
),
),
],
),
(appState.dayAvailabilityMap[index] ?? false)
? Positioned(right: 8.0, bottom: 5.0, child: activeBubble)
: Container(),
],
),
);
}
}