Created
June 24, 2020 09:55
-
-
Save prabowomurti/67d8d3d3359a8d88980930cf1c780fc9 to your computer and use it in GitHub Desktop.
Input Screen
This file contains 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 characters
import 'package:retroapp/variables.dart'; | |
import 'package:cloud_firestore/cloud_firestore.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:retroapp/lounge_screen.dart'; | |
import 'package:retroapp/sign_in.dart'; | |
import 'package:retroapp/login_screen.dart'; | |
final String setting = 'Setting'; | |
final String signOut = 'Sign Out'; | |
final String submit = 'Submit'; | |
final Firestore _firestore = Firestore.instance; | |
const String hintTextAddItem = 'Speak now or forever hold your peace'; | |
const String hintTextEditItem = 'Will delete if your input is empty'; | |
/// Each items inside ListBox is related to its BoxType, init the value to an empty List | |
Map<BoxType, List<String>> items = { | |
BoxType.HAPPY: [], | |
BoxType.SAD: [], | |
BoxType.IDEA: [], | |
BoxType.MENTION: [] | |
}; | |
class InputScreen extends StatefulWidget { | |
static const String routeName = '/input_screen'; | |
final Map event; | |
InputScreen({Key key, @required this.event}) : super(key: key); | |
@override | |
_InputScreenState createState() => _InputScreenState(); | |
} | |
class _InputScreenState extends State<InputScreen> { | |
final List<Tab> myTabs = <Tab>[ | |
Tab(icon: Icon(Icons.mood, color: Colors.green[800])), | |
Tab(icon: Icon(Icons.mood_bad, color: Colors.red[900])), | |
Tab(icon: Icon(Icons.lightbulb_outline, color: Colors.yellow)), | |
Tab(icon: Icon(Icons.alternate_email)), | |
]; | |
Map event; | |
@override | |
void initState() { | |
super.initState(); | |
// throw guest, only authenticated users can access this screen | |
if (loggedInUser.toString().isEmpty) { | |
signOutGoogle(); | |
Navigator.of(context).pushAndRemoveUntil( | |
MaterialPageRoute(builder: (context) { | |
return LoginScreen(); | |
}), ModalRoute.withName('/')); | |
} | |
// assign event from widget | |
this.event = widget.event; | |
// if user has already submitted the cards, redirect to LoungeScreen | |
Future<QuerySnapshot> submittedUsers = getSubmittedUsers(); | |
submittedUsers | |
.then((submittedUsers) { | |
if (submittedUsers.documents.isNotEmpty) { | |
Navigator.push( | |
context, | |
MaterialPageRoute( | |
builder: (context) => LoungeScreen(event: this.event))); | |
} | |
}) | |
.timeout(Duration(seconds: 120)) | |
.catchError((err) { | |
print(err); | |
}); | |
} | |
/// if this collection has a value, then the user has already submitted the cards (perhaps from different device) | |
Future<QuerySnapshot> getSubmittedUsers() async { | |
QuerySnapshot docRef = await _firestore | |
.collection('events/' + event['id'] + '/users') | |
.where('user_id', isEqualTo: loggedInUser.uid) | |
.getDocuments(); | |
return docRef; | |
} | |
@override | |
Widget build(BuildContext context) { | |
return DefaultTabController( | |
length: 4, | |
child: Scaffold( | |
appBar: AppBar( | |
bottom: TabBar( | |
tabs: myTabs, | |
), | |
title: Text(event['name']), | |
actions: <Widget>[ | |
IconButton( | |
icon: Icon(Icons.send), | |
onPressed: () { | |
buildShowDialog(context, event); | |
}, | |
), | |
], | |
), | |
body: TabBarView( | |
children: <Widget>[ | |
ListBox(boxType: BoxType.HAPPY), | |
ListBox(boxType: BoxType.SAD), | |
ListBox(boxType: BoxType.IDEA), | |
ListBox(boxType: BoxType.MENTION), | |
], | |
), | |
), | |
); | |
} | |
Future buildShowDialog(BuildContext context, Map event) { | |
// checking any empty card | |
// if (Foundation.kReleaseMode) { | |
for (var item in items.values) { | |
if (item.isEmpty) { | |
return showDialog( | |
context: context, | |
builder: (_) { | |
return AlertDialog( | |
content: Text('Please fill in all cards'), | |
actions: <Widget>[ | |
FlatButton( | |
onPressed: () { | |
Navigator.pop(context); | |
}, | |
child: Text('OK'), | |
) | |
], | |
); | |
}, | |
); | |
} | |
} | |
// } | |
return showDialog( | |
context: context, | |
builder: (_) { | |
return AlertDialog( | |
content: | |
Text('Once submitted, cards can not be edited.\n\nContinue?'), | |
actions: <Widget>[ | |
FlatButton( | |
child: Text('Cancel'), | |
onPressed: () { | |
Navigator.pop(context); | |
}, | |
), | |
RaisedButton( | |
color: Colors.blueAccent, | |
child: Text('Continue'), | |
onPressed: () { | |
_submitCards(event, context); | |
}, | |
), | |
], | |
); | |
}, | |
barrierDismissible: true, | |
); | |
} | |
/// Submit all cards to firestore | |
void _submitCards(Map event, BuildContext context) { | |
// first, check if user has already submitted cards | |
Future<QuerySnapshot> submittedUsers = getSubmittedUsers(); | |
submittedUsers | |
.then((submittedUser) { | |
if (submittedUser.documents.isNotEmpty) { | |
Navigator.push( | |
context, | |
MaterialPageRoute( | |
builder: (context) => LoungeScreen(event: this.event))); | |
} | |
// TODO : add a loading indicator (?) | |
// we should wait for the checking process to full complete before running the batch | |
batchProcess(); | |
}) | |
.timeout(Duration(seconds: 120)) | |
.catchError((err) { | |
print(err); | |
}); | |
} | |
// | |
void batchProcess() { | |
// start 'transaction' | |
WriteBatch batch = _firestore.batch(); | |
// add user_id to users collection | |
batch.setData( | |
_firestore.collection('events/' + event['id'] + '/users').document(), | |
{ | |
'user_id': loggedInUser.uid, | |
'partner_id': null, | |
'timestamp': Timestamp.now(), | |
}, | |
); | |
// decrement the counter of `count_waiting_users` | |
batch.updateData(_firestore.collection('events').document(event['id']), | |
{'count_waiting_users': FieldValue.increment(-1)}); | |
// preparing data for submision | |
items.forEach((key, item) { | |
for (var index = 0; index < item.length; index++) { | |
batch.setData(_firestore.collection('cards').document(), { | |
'detail': item[index], | |
'type': boxTypes[key].toString(), | |
'event_id': event['id'], | |
'position': index, | |
'user_id': loggedInUser.uid, | |
'timestamp': Timestamp.now(), | |
}); | |
} | |
}); | |
batch.commit().catchError((err) { | |
print(err); | |
}).whenComplete(() { | |
Navigator.pop(context); // will pop the dialog stack | |
// disable back button on LoungeScreen | |
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => LoungeScreen(event: event))); | |
}); | |
} | |
} | |
class ListBox extends StatefulWidget { | |
final BoxType boxType; | |
ListBox({@required this.boxType}); | |
@override | |
_ListBoxState createState() => _ListBoxState(); | |
} | |
class _ListBoxState extends State<ListBox> { | |
bool isEditing = false; | |
int currentEditPosition; | |
IconData messageBarIcon = Icons.add; | |
String hintText = hintTextAddItem; | |
Map<BoxType, Color> colorThemes = { | |
BoxType.HAPPY: Colors.green[100], | |
BoxType.SAD: Colors.red[200], | |
BoxType.IDEA: Colors.yellow[100], | |
BoxType.MENTION: Colors.blueGrey[100] | |
}; | |
final TextEditingController tfController = TextEditingController(); | |
final ScrollController scController = ScrollController(); | |
@override | |
Widget build(BuildContext context) { | |
return Column( | |
children: <Widget>[ | |
Expanded( | |
child: Container( | |
color: colorThemes[widget.boxType], | |
child: ListView.builder( | |
itemCount: items[widget.boxType].length, | |
itemBuilder: _buildCardItems, | |
controller: scController, | |
), | |
), | |
), | |
Container( | |
child: messageBar(context), | |
), | |
], | |
); | |
} | |
Widget _buildCardItems(BuildContext context, int position) { | |
// only create the delete button on the last item | |
if (items[widget.boxType].length - 1 != position) | |
return Card( | |
color: (position == currentEditPosition ? Colors.blue[100] : null), | |
child: ListTile( | |
title: Text(items[widget.boxType][position]), | |
onTap: () { | |
_editItem(items[widget.boxType], position); | |
}, | |
), | |
); | |
return Card( | |
color: (position == currentEditPosition ? Colors.blue[100] : null), | |
child: ListTile( | |
onTap: () { | |
_editItem(items[widget.boxType], position); | |
}, | |
title: Text(items[widget.boxType][position]), | |
// only provide delete button on last item | |
trailing: IconButton( | |
icon: Icon(Icons.delete, color: Colors.pink), | |
onPressed: () { | |
return showDialog( | |
context: context, | |
builder: (_) { | |
return AlertDialog( | |
content: Text('Are you sure to delete this item?'), | |
actions: <Widget>[ | |
FlatButton( | |
child: Text('Cancel'), | |
onPressed: () { | |
Navigator.pop(context); | |
}, | |
), | |
RaisedButton( | |
color: Colors.blueAccent, | |
child: Text('Delete'), | |
onPressed: () { | |
_removeItem(); | |
Navigator.pop(context); | |
}, | |
), | |
], | |
); | |
}, | |
barrierDismissible: true, | |
); | |
}, | |
), | |
), | |
); | |
} | |
Widget messageBar(BuildContext context) { | |
return Container( | |
padding: EdgeInsets.symmetric(horizontal: 12.0), | |
decoration: BoxDecoration( | |
border: Border( | |
top: BorderSide(color: Colors.lightBlueAccent, width: 2.0), | |
), | |
), | |
child: Row( | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: <Widget>[ | |
Expanded( | |
child: TextField( | |
autofocus: true, | |
textAlignVertical: TextAlignVertical.center, | |
controller: tfController, | |
decoration: InputDecoration( | |
contentPadding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 14.0), | |
hintText: hintText, | |
border: InputBorder.none, | |
), | |
), | |
), | |
GestureDetector( | |
onTap: () { | |
if (isEditing) | |
_saveItem(currentEditPosition, tfController.text); | |
else | |
_addItem(tfController.text); | |
}, | |
child: CircleAvatar( | |
child: Icon( | |
messageBarIcon, | |
), | |
), | |
) | |
], | |
), | |
); | |
} | |
// Add item to the cardItems | |
void _addItem(String text) { | |
if (text.trim().isEmpty) return; | |
setState(() { | |
items[widget.boxType].add(text); | |
tfController.clear(); | |
scController.jumpTo(scController.position.maxScrollExtent + | |
80.0); // TODO: need to change the workaround | |
}); | |
} | |
// Save item to the new value | |
void _saveItem(int position, String newText) { | |
if (newText.trim().isEmpty) | |
_removeItem(position); | |
else | |
items[widget.boxType][position] = newText; | |
setState(() { | |
_resetMode(); | |
}); | |
} | |
// Editing the item at position | |
void _editItem(List<String> cardItems, int position) { | |
// if the same card is tapped again, cancel editing | |
if (isEditing && position == currentEditPosition) | |
{ | |
_resetMode(); | |
setState(() {}); | |
return; | |
} | |
isEditing = true; | |
currentEditPosition = position; | |
hintText = hintTextEditItem; | |
setState(() { | |
tfController.text = cardItems[position]; | |
// TODO: find another workaround to put the cursor at the end of the selection | |
tfController.selection = | |
TextSelection.collapsed(offset: cardItems[position].length); | |
messageBarIcon = Icons.refresh; | |
}); | |
} | |
// remove item at position. If position is not specified, remove the last one | |
void _removeItem([int position]) { | |
if (position == null) | |
items[widget.boxType].removeLast(); | |
else | |
items[widget.boxType].removeAt(position); | |
_resetMode(); | |
setState(() {}); | |
} | |
// Reset all needed variables to init state | |
void _resetMode() { | |
isEditing = false; | |
currentEditPosition = null; | |
messageBarIcon = Icons.add; | |
tfController.clear(); | |
hintText = hintTextAddItem; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment