Last active
September 26, 2018 18:10
-
-
Save AlexKorovyansky/066f69bba0b7fa5affed49f4b444e6e4 to your computer and use it in GitHub Desktop.
Revisions
-
AlexKorovyansky revised this gist
Sep 26, 2018 . 1 changed file with 66 additions and 59 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -90,11 +90,11 @@ class ReordableTodoListView extends StatelessWidget { final orderMutation = List<double>(); // If we want to drag&drop element X between elements Y&Z // that's enough to change only X.order to (Y.order+Z.order)*0.5 // // If we want to move element to first or to last position // we use -current_time and +current as not the ideal but // prototype valid solution. // // The approach itself is inspired by Trello API. for (var i = 0; i < documents.data.length - 1; i++) { final order1 = documents.data[i]['order'] ?? 0.0; @@ -107,9 +107,9 @@ class ReordableTodoListView extends StatelessWidget { debugPrint('hackList = $orderMutation'); documents.data.toList().fold<List<double>>( [-DateTime.now().millisecondsSinceEpoch.toDouble()].toList(), (list, order) { return list; }); return Container( child: ReorderableListView( padding: EdgeInsets.all(8.0), @@ -121,11 +121,13 @@ class ReordableTodoListView extends StatelessWidget { .updateData({'order': orderMutation[newIndex]}); }, children: documents.data .map((document) { return TodoWidget( key: ValueKey(document.documentID), checked: document['checked'], text: document['todo'], documentId: document .documentID, //todo: try to pass text & checked here? );}) .toList()), // child: Column( // children: documents.data @@ -156,64 +158,67 @@ class _TodoListWidgetState extends State<TodoListWidget> { Widget build(BuildContext context) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Padding( padding: const EdgeInsets.only(left: 16.0), child: Row( children: <Widget>[ Flexible( child: Text( widget.title, style: TextStyle( fontSize: 20.0, fontWeight: FontWeight.bold, ), ), ), IconButton( icon: Icon(Icons.add), onPressed: () { widget.addPressed(); }, ), ], ), ), StreamBuilder<List<dynamic>>( stream: widget.stream, builder: (context, documents) { if (documents.data == null) return Container(); return Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: List<Widget>() ..addAll(documents.data .map((documentSnapshot) { return TodoWidget( key: Key(documentSnapshot.documentID), checked: documentSnapshot["checked"], documentId: documentSnapshot.documentID); }) .fold<List<Widget>>( List<Widget>()..add(Text('123')), (current, widget) => current..addAll([widget, Text('123')])) .toList()))); }, ), ], ); }); } } class TodoWidget extends StatefulWidget { final String documentId; final String text; final bool checked; TodoWidget({Key key, this.documentId, this.text = "", this.checked = false}) : super(key: key); @override _TodoWidgetState createState() => _TodoWidgetState(); @@ -228,15 +233,17 @@ class _TodoWidgetState extends State<TodoWidget> { @override void initState() { super.initState(); this.checked = widget.checked; this.outController.text = widget.text; debugPrint('initState $widget.documentId, $checked, ${widget.text}'); subscription = Firestore.instance .collection('todos') .document(widget.documentId) .snapshots() .listen((documentSnapshot) { setState(() { outController.text = documentSnapshot.exists ? documentSnapshot["todo"] : "[none]"; checked = documentSnapshot.exists ? documentSnapshot["checked"] ?? false : false; @@ -285,7 +292,7 @@ class _TodoWidgetState extends State<TodoWidget> { .collection('todos') .document(widget.documentId) .updateData( {'checked': changedChecked, 'todo': outController.text}); } }, ), @@ -297,7 +304,7 @@ class _TodoWidgetState extends State<TodoWidget> { }, style: ((outController.text.trim() != '') ? checked : false) ? TextStyle( color: Colors.black, decoration: TextDecoration.lineThrough) : TextStyle(color: Colors.black), enabled: !(((outController.text.trim() != '') ? checked : false)), decoration: InputDecoration( @@ -338,4 +345,4 @@ class MyTextEditingController extends TextEditingController { composing: TextRange.empty); } } } -
AlexKorovyansky created this gist
Sep 26, 2018 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,341 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:flutter/rendering.dart'; import 'package:rxdart/rxdart.dart'; import 'dart:async'; void main() { Firestore.instance.enablePersistence(true); FirebaseDatabase.instance.setPersistenceEnabled(false); runApp(MaterialApp(title: "Happy.do", home: HappyDo())); } class HappyDo extends StatelessWidget { @override Widget build(BuildContext context) { return DefaultTabController( length: 3, child: Scaffold( appBar: AppBar( title: Text('Happy.do'), bottom: TabBar( tabs: [ Tab( text: 'Today', ), Tab(text: 'This week'), Tab(text: 'Next week'), ], )), body: TabBarView( children: <Widget>[ ReordableTodoListView( stream: Observable<QuerySnapshot>(Firestore.instance .collection('todos') .where('date', isEqualTo: '2018-09-25') .where('weekly', isEqualTo: false) .snapshots()), ), ReordableTodoListView( stream: Observable<QuerySnapshot>(Firestore.instance .collection('todos') .where('date', isGreaterThanOrEqualTo: '2018-09-24') .where('date', isLessThanOrEqualTo: '2018-09-30') .where('weekly', isEqualTo: false) .snapshots()), ), ReordableTodoListView( stream: Observable<QuerySnapshot>(Firestore.instance .collection('todos') .where('date', isGreaterThanOrEqualTo: '2018-10-01') .where('date', isLessThanOrEqualTo: '2018-10-07') .where('weekly', isEqualTo: true) .snapshots()), ), ], ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () async { await Firestore.instance.collection('todos').document().setData({ 'order': -DateTime.now().millisecondsSinceEpoch.toDouble(), 'todo': '', 'date': '2018-09-25', 'weekly': false, 'checked': false }); }, ), ), ); } } class ReordableTodoListView extends StatelessWidget { final Observable<QuerySnapshot> stream; ReordableTodoListView({Key key, @required this.stream}) : super(key: key); @override Widget build(BuildContext context) { return StreamBuilder<List<DocumentSnapshot>>( stream: stream.map((s) => s.documents ..sort((d1, d2) => ((d1.data['order'] ?? 0) - (d2.data['order'] ?? 0)).toInt())), builder: (context, documents) { if (documents.data == null) return Container(); final orderMutation = List<double>(); // If we want to drag&drop element X between elements Y&Z // that's enough to change only X.order to (Y.order+Z.order)*0.5 // // If we want to move element to first or to last position // we use -current_time and +current as not the ideal but // prototype valid solution. // // The approach itself is inspired by Trello API. for (var i = 0; i < documents.data.length - 1; i++) { final order1 = documents.data[i]['order'] ?? 0.0; final order2 = documents.data[i + 1]['order'] ?? 0.0; orderMutation.add((order1 + order2) * 0.5); } orderMutation.insert(0, -DateTime.now().millisecondsSinceEpoch.toDouble()); orderMutation.insert(orderMutation.length, DateTime.now().millisecondsSinceEpoch.toDouble()); debugPrint('hackList = $orderMutation'); documents.data.toList().fold<List<double>>( [-DateTime.now().millisecondsSinceEpoch.toDouble()].toList(), (list, order) { return list; }); return Container( child: ReorderableListView( padding: EdgeInsets.all(8.0), onReorder: (oldIndex, newIndex) async { debugPrint('$oldIndex, $newIndex'); await Firestore.instance .collection('todos') .document(documents.data[oldIndex].documentID) .updateData({'order': orderMutation[newIndex]}); }, children: documents.data .map((document) => TodoWidget( key: ValueKey(document.documentID), documentId: document .documentID, //todo: try to pass text & checked here? )) .toList()), // child: Column( // children: documents.data // .map((document) => TodoWidget( // key: GlobalObjectKey(document.documentID.hashCode), // documentId: document.documentID, // )) // .toList()), ); }); } } class TodoListWidget extends StatefulWidget { final String title; final Observable<List<dynamic>> stream; final Function addPressed; TodoListWidget({Key key, this.title, this.stream, this.addPressed}) : super(key: key); @override _TodoListWidgetState createState() => _TodoListWidgetState(); } class _TodoListWidgetState extends State<TodoListWidget> { @override Widget build(BuildContext context) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Padding( padding: const EdgeInsets.only(left: 16.0), child: Row( children: <Widget>[ Flexible( child: Text( widget.title, style: TextStyle( fontSize: 20.0, fontWeight: FontWeight.bold, ), ), ), IconButton( icon: Icon(Icons.add), onPressed: () { widget.addPressed(); }, ), ], ), ), StreamBuilder<List<dynamic>>( stream: widget.stream, builder: (context, documents) { if (documents.data == null) return Container(); return Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: List<Widget>() ..addAll(documents.data .map((documentSnapshot) { return TodoWidget( key: Key(documentSnapshot.documentID), documentId: documentSnapshot.documentID); }) .fold<List<Widget>>( List<Widget>()..add(Text('123')), (current, widget) => current..addAll([widget, Text('123')])) .toList()))); }, ), ], ); }); } } class TodoWidget extends StatefulWidget { final String documentId; TodoWidget({Key key, this.documentId}) : super(key: key); @override _TodoWidgetState createState() => _TodoWidgetState(); } class _TodoWidgetState extends State<TodoWidget> { MyTextEditingController outController = MyTextEditingController(text: ''); BehaviorSubject<String> subject; StreamSubscription<DocumentSnapshot> subscription; bool checked = false; @override void initState() { super.initState(); debugPrint('initState ' + widget.documentId); subscription = Firestore.instance .collection('todos') .document(widget.documentId) .snapshots() .listen((documentSnapshot) { setState(() { outController.text = documentSnapshot.exists ? documentSnapshot["todo"] : "[none]"; checked = documentSnapshot.exists ? documentSnapshot["checked"] ?? false : false; }); }); subject = BehaviorSubject<String>(); subject.debounce(Duration(milliseconds: 750)).listen((text) { Firestore.instance .collection('todos') .document(widget.documentId) .updateData({'todo': text, 'checked': checked}); }); } @override void didUpdateWidget(TodoWidget oldWidget) { debugPrint('didUpdateWidget ' + widget.documentId + ' from ' + oldWidget.documentId); super.didUpdateWidget(oldWidget); } @override void dispose() { debugPrint('dispose ' + widget.documentId); subject.close(); subscription.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return _buildTodoRow(); } Row _buildTodoRow() { return Row( children: <Widget>[ Checkbox( value: (outController.text.trim() != '') ? checked : false, onChanged: (changedChecked) async { if (outController.text.trim() != '') { // TODO: cancel deferred events on subject await Firestore.instance .collection('todos') .document(widget.documentId) .updateData( {'checked': changedChecked, 'todo': outController.text}); } }, ), Flexible( child: TextField( controller: outController, onChanged: (text) { subject.add(text); }, style: ((outController.text.trim() != '') ? checked : false) ? TextStyle( color: Colors.black, decoration: TextDecoration.lineThrough) : TextStyle(color: Colors.black), enabled: !(((outController.text.trim() != '') ? checked : false)), decoration: InputDecoration( border: InputBorder.none, hintText: "enter new todo here"), ), ), IconButton( icon: Icon(Icons.code), ), IconButton( icon: Icon(Icons.delete), onPressed: () async { await Firestore.instance .collection('todos') .document(widget.documentId) .delete(); }, ) ], ); } } /// workaround for https://github.com/flutter/flutter/issues/22171 class MyTextEditingController extends TextEditingController { MyTextEditingController({String text}) : super(text: text); set text(String newText) { try { value = value.copyWith( text: newText); } catch (e) { value = value.copyWith( text: newText, selection: TextSelection.collapsed(offset: -1), composing: TextRange.empty); } } }