diff --git a/ltx_flutter/android/app/src/main/AndroidManifest.xml b/ltx_flutter/android/app/src/main/AndroidManifest.xml index 361b8fd..641d9c2 100644 --- a/ltx_flutter/android/app/src/main/AndroidManifest.xml +++ b/ltx_flutter/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ + element.data['number'] == -1); notifyListeners(); _ready = true; } diff --git a/ltx_flutter/lib/helpers.dart b/ltx_flutter/lib/helpers.dart new file mode 100644 index 0000000..e70b267 --- /dev/null +++ b/ltx_flutter/lib/helpers.dart @@ -0,0 +1,64 @@ +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: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); + final date = dateISO!.isEmpty ? DateTime.now() : DateTime.parse(dateISO); + return dateFormatter.format(date); +} + +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 > 24) { + hour = e - 24; + } else if (e > 12) { + hour = e - 12; + } else if (e > 0) { + hour = e; + } + if (e > 11 && e < 24) { + meridien = "PM"; + } + return "${hour.toString()} $meridien"; +} + +Color moodColor(mood) { + if (mood == null) { + return Colors.transparent; + } + if (mood >= 8) { + return Colors.green; + } + if (mood > 5) { + return Colors.blue; + } + if (mood == 5) { + return Colors.yellow; + } + if (mood >= 3) { + return Colors.amber; + } else { + return Colors.red; + } +} diff --git a/ltx_flutter/lib/pages/today_page.dart b/ltx_flutter/lib/pages/today_page.dart index db0ef63..099dd30 100644 --- a/ltx_flutter/lib/pages/today_page.dart +++ b/ltx_flutter/lib/pages/today_page.dart @@ -6,62 +6,9 @@ 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); - final date = dateISO!.isEmpty ? DateTime.now() : DateTime.parse(dateISO); - return dateFormatter.format(date); -} - -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 > 24) { - hour = e - 24; - } else if (e > 12) { - hour = e - 12; - } else if (e > 0) { - hour = e; - } - if (e > 11 && e < 24) { - meridien = "PM"; - } - return "${hour.toString()} $meridien"; -} - -Color moodColor(mood) { - if (mood == null) { - return Colors.transparent; - } - if (mood >= 8) { - return Colors.green; - } - if (mood > 5) { - return Colors.blue; - } - if (mood == 5) { - return Colors.yellow; - } - if (mood >= 3) { - return Colors.amber; - } else { - return Colors.red; - } -} +import 'today_views/day_view.dart'; +import 'today_views/infinity_view.dart'; +import '../helpers.dart'; class TodayPage extends StatefulWidget { const TodayPage({Key? key}) : super(key: key); @@ -113,11 +60,12 @@ class NarrowView extends StatelessWidget { ), Expanded( child: TabBarView( + physics: const NeverScrollableScrollPhysics(), children: [ DayView(), Icon(Icons.directions_transit, size: 350), Icon(Icons.directions_car, size: 350), - Icon(Icons.abc), + InfinityView(), ], ), ) @@ -126,510 +74,3 @@ class NarrowView extends StatelessWidget { ); } } - -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 = null; - 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 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 (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/lib/pages/today_views/day_view.dart b/ltx_flutter/lib/pages/today_views/day_view.dart new file mode 100644 index 0000000..06bf18e --- /dev/null +++ b/ltx_flutter/lib/pages/today_views/day_view.dart @@ -0,0 +1,528 @@ +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:intl/intl.dart'; +import 'package:appwrite/models.dart'; +import 'package:timezone/timezone.dart' as tz; +import 'package:timezone/data/latest.dart' as tz; +import 'package:ltx_flutter/pages/today_page.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 = null; + 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), + ], + ); + } +} diff --git a/ltx_flutter/lib/pages/today_views/infinity_view.dart b/ltx_flutter/lib/pages/today_views/infinity_view.dart new file mode 100644 index 0000000..d0e01a6 --- /dev/null +++ b/ltx_flutter/lib/pages/today_views/infinity_view.dart @@ -0,0 +1,103 @@ +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:intl/intl.dart'; +import 'package:appwrite/models.dart'; +import 'package:timezone/timezone.dart' as tz; +import 'package:timezone/data/latest.dart' as tz; +import 'package:ltx_flutter/pages/today_page.dart'; +import 'package:ltx_flutter/helpers.dart'; + +class InfinityView extends StatefulWidget { + const InfinityView({ + super.key, + }); + + @override + State createState() => _InfinityViewState(); +} + +class _InfinityViewState extends State { + DateTime _date = DateTime.now(); + + late DatabaseAPI database; + late CategoriesAPI categories; + + late List entries = []; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + database = context.watch(); + categories = context.watch(); + entries = database.entries; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Container( + height: MediaQuery.of(context).size.height, + child: GridView.builder( + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 24), + padding: EdgeInsets.all(2), + itemBuilder: (context, index) { + int entryIndex = (-1 + (index / 24)).toInt(); + if (index < 24) { + // Build the top row + return Text(""); + } else if (entryIndex < entries.length) { + Document entry = entries[entryIndex]; + if (index % 24 == 0) { + // First column, show date + return Text( + formatDate( + dateISO: (entry.data['date']).toString(), + format: "MM/dd"), + ); + } else { + int hourIndex = (index % 24) - 1; + num? hour; + try { + hour = entry.data['hours'][hourIndex]; + } catch (e) { + hour = null; + } + + String tooltip; + Color fgColor; + Color bgColor; + + try { + Category category = categories.lookUp(hour.toString()); + tooltip = category.description!; + fgColor = category.foregroundColor; + bgColor = category.backgroundColor; + } catch (e) { + tooltip = ""; + fgColor = Colors.white; + bgColor = Colors.black; + } + return SizedBox( + width: 50, + height: 50, + child: Tooltip( + message: tooltip, + child: Container( + color: bgColor, + child: Center( + child: Text( + style: TextStyle(fontSize: 10, color: fgColor), + hour.toString())), + ), + ), + ); + } + } + }), + ), + ); + } +}