import 'package:ltx_flutter/appwrite/categories_api.dart'; import 'package:ltx_flutter/appwrite/database_api.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:appwrite/models.dart'; import 'package:ltx_flutter/helpers.dart'; class DayView extends StatefulWidget { const DayView({ super.key, }); @override State createState() => _DayViewState(); } class _DayViewState extends State { DateTime _date = DateTime.now(); late DatabaseAPI database; late CategoriesAPI categories; late List entries = []; late Document? dayEntry; late List hours = []; late num? mood = 0; late String comments = ""; bool _editable = false; @override void didChangeDependencies() { super.didChangeDependencies(); database = context.watch(); categories = context.watch(); entries = database.entries; } void _incrementDate(int amount) => _setDate(_date.add(Duration(days: amount))); void _setDate(DateTime? day) { if (day != null) { setState(() { _date = day; }); } } @override Widget build(BuildContext context) { List entries = database.entries; String formattedDate = formatDate( dateISO: _date.toIso8601String(), format: "yyyy-MM-dd", ); try { dayEntry = entries.singleWhere((element) => element.$id == formattedDate); String date = formatDate( format: "LLL d", dateISO: dayEntry?.data['date'].toString()); print("Got entry for $date"); } catch (e) { database.getOne(date: formattedDate).then((value) => dayEntry = value); } setState(() { if (dayEntry != null) { comments = dayEntry?.data["comments"]; mood = dayEntry?.data["mood"]; } }); TextEditingController commentsController = TextEditingController(text: comments); Widget moodWidget = mood == null ? Icon( size: 30, Icons.star_outline, color: Colors.white, ) : Text( style: TextStyle(fontSize: 20, color: Colors.black), mood.toString()); return GestureDetector( onHorizontalDragEnd: (details) { if (details.primaryVelocity!.abs() > 10) { details.primaryVelocity! < 0 ? _incrementDate(1) : _incrementDate(-1); } }, child: Column( children: [ SizedBox( height: 20, ), Padding( padding: const EdgeInsets.all(8), child: GestureDetector( onTap: () => _setDate(DateTime.now()), onLongPress: () { showDatePicker( context: context, initialDate: _date, firstDate: DateTime.parse("2023-01-01"), lastDate: DateTime.now().add(Duration(days: 7)), ).then((value) => _setDate(value)); }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( tooltip: "Previous day", icon: Icon(Icons.arrow_left), onPressed: () => _incrementDate(-1), ), SizedBox( width: 250, child: Text( textAlign: TextAlign.center, style: TextStyle( fontSize: 20, ), formatDate( format: 'EEEEE, LLLL dd, yyyy', dateISO: _date.toIso8601String()), ), ), IconButton( tooltip: "Next day", icon: Icon(Icons.arrow_right), onPressed: () => _incrementDate(1), ), Expanded( child: SizedBox(width: 5), ), Text("Edit"), Switch( value: _editable, onChanged: (value) => setState(() { _editable = !_editable; }), ), ], ), ), ), Expanded( child: categories.ready ? HourGenerator( dayEntry: dayEntry, editable: _editable, ) : ListView( children: [Center(child: CircularProgressIndicator())]), ), Padding( padding: const EdgeInsets.only(top: 30, right: 30, left: 10), child: Row( children: [ SizedBox(width: 10), SizedBox.square( dimension: 50, child: GestureDetector( onTapDown: (event) => showMenu( initialValue: mood, context: context, position: RelativeRect.fromLTRB( event.globalPosition.dx + 25, event.globalPosition.dy - 50, MediaQuery.of(context).size.width - event.globalPosition.dx, MediaQuery.of(context).size.height - event.globalPosition.dy + 0, ), items: List.generate( 10, (index) => PopupMenuItem( onTap: () => database.updateEntry( dateISO: dayEntry!.data['date'], mood: index), value: index, child: Text( style: TextStyle( fontWeight: FontWeight.bold, color: moodColor(index), ), index.toString(), ), ), ), ), child: mood == null ? Container( decoration: BoxDecoration( border: Border.all(color: Colors.white)), child: Center(child: moodWidget), ) : Container( color: moodColor(mood), child: Center(child: moodWidget), ), ), ), SizedBox(width: 30), Expanded( child: TextFormField( decoration: InputDecoration(hintText: "Comments"), smartQuotesType: SmartQuotesType.enabled, enableInteractiveSelection: true, controller: commentsController, minLines: 2, maxLines: 4, onFieldSubmitted: (value) { database.updateEntry( dateISO: dayEntry!.data['date'], comments: value); }, ), ), SizedBox(width: 30), IconButton( tooltip: "Save", onPressed: () => database.updateEntry( dateISO: dayEntry!.data['date'], comments: commentsController.value.text.toString()), icon: Icon(Icons.save), ) ], ), ), SizedBox(height: 30), ], ), ); } } class HourGenerator extends StatefulWidget { const HourGenerator( {super.key, required this.dayEntry, required this.editable}); final Document? dayEntry; final bool editable; @override State createState() => _HourGeneratorState(); } class _HourGeneratorState extends State { @override Widget build(BuildContext context) { List generateHours(entry, bool edit) { if (entry == null) { return [Center(child: RefreshProgressIndicator())]; } List hours = entry.data['hours']; // print(hours); List reduced = []; int counter = 0; if (!edit) { for (num val in hours) { if (reduced.isEmpty) { reduced.add({'val': val, 'num': 1, 'hour': counter}); } else { if (reduced.last['val'] == val) { reduced.last['num']++; reduced.last['hour'] = counter; } else { reduced.add({'val': val, 'num': 1, 'hour': counter}); } } counter++; } } else { for (num val in hours) { reduced.add({ 'hour': counter, 'val': val, 'num': 1, }); if (edit) counter++; } } List hourWidgets = reduced.map( (e) { double height = double.parse((e['num'] * 36).toString()); return Consumer( builder: (context, categories, child) { Category category = categories.lookUp(e['val'].toString()); return SizedBox( height: height, child: GestureDetector( onTap: () { setState(() { edit = true; }); }, child: Card( color: category.backgroundColor, child: Padding( padding: const EdgeInsets.only(left: 10), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ e['num'] == 1 ? SizedBox( width: 50, child: Center( child: Text( style: TextStyle( color: category.foregroundColor), hourString(e['hour']), ), ), ) : Padding( padding: const EdgeInsets.only( left: 5, top: 10, bottom: 10), child: Column( children: [ Text( style: TextStyle( color: category.foregroundColor), hourString(e['hour'] - e['num'] + 1)), Expanded( child: VerticalDivider( indent: 2, endIndent: 2, color: category.foregroundColor, thickness: 2, ), ), Text( style: TextStyle( color: category.foregroundColor), hourString(e['hour'] + 1)) ], ), ), Expanded( child: Center( child: edit ? HourFormField( category: category, index: e['hour'], dayEntry: entry, ) : Text( style: TextStyle( color: category.foregroundColor), category.name), ), ), ], ), ), ), ), ); }, ); }, ).toList(); List> remainingHours = List.generate(24 - counter, (index) { return Consumer(builder: (context, value, child) { return SizedBox( height: 35, child: Card( color: Colors.black, child: Padding( padding: const EdgeInsets.only(left: 10), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 50, child: Center( child: Text( style: TextStyle(color: Colors.white), hourString(counter + index)), ), ), Expanded( child: Center( child: HourFormField( index: index + counter, dayEntry: entry, ), ), ), ], ), ), ), ); }); }); List> remainingHoursConsolidated = [ Consumer( builder: (context, value, child) { return SizedBox( height: (24 - counter) * 30, child: Card( color: Colors.black, child: Padding( padding: const EdgeInsets.only(left: 10), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(top: 10, bottom: 10), child: Column( children: [ Text( style: TextStyle(color: Colors.white), hourString(counter)), Expanded( child: VerticalDivider( indent: 2, endIndent: 2, color: Colors.white, thickness: 2, ), ), Text( style: TextStyle(color: Colors.white), hourString(24)) ], ), ), Expanded( child: Center( child: Icon(Icons.question_mark), ), ), ], ), ), ), ); }, ) ]; edit || counter == 23 ? hourWidgets.addAll(remainingHours) : hourWidgets.addAll(remainingHoursConsolidated); return hourWidgets; } return ListView(children: generateHours(widget.dayEntry, widget.editable)); } } class HourFormField extends StatefulWidget { HourFormField({ super.key, this.category, required this.index, required this.dayEntry, }); final Category? category; int index; Document dayEntry; @override State createState() => _HourFormFieldState(); } class _HourFormFieldState extends State { Color fgColor = Colors.white; String catNum = ""; String catName = "[Empty]"; @override Widget build(BuildContext context) { if (widget.category != null) { catNum = widget.category!.number.toString(); fgColor = widget.category!.foregroundColor; catName = widget.category!.name; } return Row( children: [ SizedBox( width: 50, ), SizedBox( width: 50, child: Consumer(builder: (context, database, child) { return TextFormField( showCursor: false, onFieldSubmitted: (value) { database.updateHours(widget.dayEntry, widget.index, value); }, style: TextStyle( color: fgColor, fontFamily: "Monospace", height: 35), textAlign: TextAlign.center, keyboardType: TextInputType.numberWithOptions( signed: false, ), initialValue: catNum, ); }), ), SizedBox(width: 40), Text( style: TextStyle(color: fgColor, fontFamily: "Monospace"), catName), ], ); } }