diff --git a/ltx_flutter/lib/appwrite/database_api.dart b/ltx_flutter/lib/appwrite/database_api.dart index e847ee3..340f59f 100644 --- a/ltx_flutter/lib/appwrite/database_api.dart +++ b/ltx_flutter/lib/appwrite/database_api.dart @@ -137,6 +137,49 @@ class DatabaseAPI extends ChangeNotifier { return response.documents; } + Future updateEntry( + {String? dateISO, + List? hours, + int? mood, + String? comments}) async { + String date = formatter.format(DateTime.parse(dateISO!)); + + int entryIndex = _entries.indexWhere((element) => elDate(element) == date); + + hours ??= _entries[entryIndex].data['hours']; + comments ??= _entries[entryIndex].data['comments']; + mood ??= _entries[entryIndex].data['mood']; + + Document newEntry = await databases.updateDocument( + databaseId: APPWRITE_DATABASE_ID, + collectionId: COLLECTION, + documentId: date, + data: {'hours': hours, 'mood': mood, 'comments': comments}, + ); + + _entries.removeAt(entryIndex); + _entries.add(newEntry); + notifyListeners(); + return newEntry; + } + + updateHours(dayEntry, index, value) { + List hours = dayEntry.data['hours']; + try { + hours[index] = num.parse(value); + } catch (e) { + if (hours.length == index) { + hours.add(num.parse(value)); + } else { + print(List.generate(index - hours.length, (i) => -1)); + + hours.addAll(List.generate(index - hours.length, (i) => -1)); + hours.add(num.parse(value)); + } + } + updateEntry(dateISO: dayEntry.data['date'], hours: hours); + } + Future addEntry( {required String date, List hours = const [], diff --git a/ltx_flutter/lib/pages/tabs_page.dart b/ltx_flutter/lib/pages/tabs_page.dart index 9ad730d..3bc68e6 100644 --- a/ltx_flutter/lib/pages/tabs_page.dart +++ b/ltx_flutter/lib/pages/tabs_page.dart @@ -32,27 +32,6 @@ class _TabsPageState extends State { builder: (context, snapshot) { return Text(snapshot.data.toString()); }), - actions: [ - Padding( - padding: const EdgeInsets.only(right: 28.0), - child: Row( - children: [ - Text("Edit"), - StreamBuilder( - stream: appBloc.editable, - initialData: true, - builder: (context, snapshot) { - return Switch( - value: bool.parse(snapshot.data.toString()), - onChanged: (value) { - // print(value); - }, - ); - }), - ], - ), - ) - ], ), body: _widgets.elementAt(_selectedIndex), bottomNavigationBar: BottomNavigationBar( diff --git a/ltx_flutter/lib/pages/today_page.dart b/ltx_flutter/lib/pages/today_page.dart index f6e15a8..db0ef63 100644 --- a/ltx_flutter/lib/pages/today_page.dart +++ b/ltx_flutter/lib/pages/today_page.dart @@ -4,6 +4,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:intl/intl.dart'; import 'package:appwrite/models.dart'; +import 'package:timezone/timezone.dart' as tz; +import 'package:timezone/data/latest.dart' as tz; String formatDate({String format = "", String? dateISO}) { final DateFormat dateFormatter = DateFormat(format); @@ -12,117 +14,35 @@ String formatDate({String format = "", String? dateISO}) { } String hourString(int e) { + try { + tz.getLocation('America/Los_Angeles'); + } catch (e) { + tz.initializeTimeZones(); + } + + int pacificTime = tz.TZDateTime.now(tz.getLocation('America/Los_Angeles')) + .timeZoneOffset + .inHours; + int localTime = DateTime.now().timeZoneOffset.inHours; + + e = e + localTime - pacificTime; + var meridien = "AM"; var hour = 12; - if (e > 12) { + + if (e > 24) { + hour = e - 24; + } else if (e > 12) { hour = e - 12; } else if (e > 0) { hour = e; } - if (e > 11) { + if (e > 11 && e < 24) { meridien = "PM"; } return "${hour.toString()} $meridien"; } -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 (int 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 (int val in hours) { - reduced.add({ - 'hour': counter, - 'val': val, - 'num': 1, - }); - } - } - - return 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: Card( - color: category.backgroundColor, - child: Padding( - padding: const EdgeInsets.only(left: 10), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - e['num'] == 1 - ? Center( - child: Text( - style: - TextStyle(color: category.foregroundColor), - hourString(e['hour'])), - ) - : Padding( - padding: const EdgeInsets.only(top: 10, bottom: 10), - child: Column( - children: [ - Text( - style: TextStyle( - color: category.foregroundColor), - hourString(e['hour'] - e['num'] + 1)), - Expanded( - child: VerticalDivider( - indent: 10, - endIndent: 10, - color: category.foregroundColor, - width: 4, - ), - ), - Text( - style: TextStyle( - color: category.foregroundColor), - hourString(e['hour'] + 1)) - ], - ), - ), - Expanded( - child: Center( - child: Text( - style: TextStyle(color: category.foregroundColor), - category.name), - ), - ), - ], - ), - ), - ), - ); - }, - ); - }, - ).toList(); -} - Color moodColor(mood) { if (mood == null) { return Colors.transparent; @@ -228,6 +148,8 @@ class _DayViewState extends State { late num? mood = 0; late String comments = ""; + bool _editable = false; + @override void didChangeDependencies() { super.didChangeDependencies(); @@ -277,8 +199,8 @@ class _DayViewState extends State { Widget moodWidget = mood == null ? Icon( size: 30, - Icons.star_rate_outlined, - color: Colors.black, + Icons.star_outline, + color: Colors.white, ) : Text( style: TextStyle(fontSize: 20, color: Colors.black), @@ -290,7 +212,7 @@ class _DayViewState extends State { height: 20, ), Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: GestureDetector( onTap: () => _setDate(DateTime.now()), onLongPress: () { @@ -326,16 +248,28 @@ class _DayViewState extends State { 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: ListView( - children: categories.ready - ? generateHours(dayEntry, true) - : [CircularProgressIndicator()], - ), + child: categories.ready + ? HourGenerator( + dayEntry: dayEntry, + editable: _editable, + ) + : ListView( + children: [Center(child: CircularProgressIndicator())]), ), Padding( padding: const EdgeInsets.only(top: 30, right: 30, left: 10), @@ -344,9 +278,45 @@ class _DayViewState extends State { SizedBox(width: 10), SizedBox.square( dimension: 50, - child: Container( - color: moodColor(mood), - child: Center(child: moodWidget), + 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), @@ -356,12 +326,20 @@ class _DayViewState extends State { 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: () => print("Save"), + onPressed: () => database.updateEntry( + dateISO: dayEntry!.data['date'], + comments: commentsController.value.text.toString()), icon: Icon(Icons.save), ) ], @@ -372,3 +350,286 @@ class _DayViewState extends State { ); } } + +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 (int 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 (int 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: () {}, + 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( + 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: 135, + ), + 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), + ], + ); + } +} diff --git a/ltx_flutter/pubspec.yaml b/ltx_flutter/pubspec.yaml index 28024ab..c784055 100644 --- a/ltx_flutter/pubspec.yaml +++ b/ltx_flutter/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: pluto_grid: ^7.0.2 string_to_hex: ^0.2.2 flutter_form_builder: ^9.0.0 + timezone: ^0.9.2 dev_dependencies: flutter_test: