From dfeb19126e27ae75de0d896b40b1d3824b966912 Mon Sep 17 00:00:00 2001 From: Ryan Pandya Date: Sat, 3 Jun 2023 00:21:09 -0400 Subject: [PATCH] Got a first draft of TodayPage working! --- ltx_flutter/lib/appwrite/categories_api.dart | 13 + ltx_flutter/lib/appwrite/database_api.dart | 64 ++- ltx_flutter/lib/pages/_old_today_page.dart | 132 ++++++ ltx_flutter/lib/pages/account_page.dart | 2 +- ltx_flutter/lib/pages/tabs_page.dart | 2 +- ltx_flutter/lib/pages/today_page.dart | 413 ++++++++++++++----- 6 files changed, 506 insertions(+), 120 deletions(-) create mode 100644 ltx_flutter/lib/pages/_old_today_page.dart diff --git a/ltx_flutter/lib/appwrite/categories_api.dart b/ltx_flutter/lib/appwrite/categories_api.dart index ea17cc0..d13bad9 100644 --- a/ltx_flutter/lib/appwrite/categories_api.dart +++ b/ltx_flutter/lib/appwrite/categories_api.dart @@ -79,6 +79,7 @@ class CategoriesAPI extends ChangeNotifier { late final Account account; late final Databases databases; late List _categories = []; + bool _ready = false; // Constructor CategoriesAPI() { @@ -92,6 +93,17 @@ class CategoriesAPI extends ChangeNotifier { get isEmpty => _categories.isEmpty; get(n) => Category(_categories[n]); get colors => CategoryColor.values; + get ready => _ready; + + lookUp(n) { + if (!_ready) { + return false; + } + print(_ready); + return Category(_categories.singleWhere((e) { + return e.data['number'] == double.parse(n.toString()); + })); + } init() { client.setEndpoint(APPWRITE_URL).setProject(APPWRITE_PROJECT_ID); @@ -112,6 +124,7 @@ class CategoriesAPI extends ChangeNotifier { ]); _categories = response.documents; notifyListeners(); + _ready = true; } Future? addCategory({ diff --git a/ltx_flutter/lib/appwrite/database_api.dart b/ltx_flutter/lib/appwrite/database_api.dart index 8fc6491..4e1926c 100644 --- a/ltx_flutter/lib/appwrite/database_api.dart +++ b/ltx_flutter/lib/appwrite/database_api.dart @@ -12,17 +12,20 @@ class DatabaseAPI extends ChangeNotifier { final AuthAPI auth = AuthAPI(); late List _entries = []; + bool _ready = false; // Getter methods List get entries => _entries; - int? get total => _entries.length; + int get length => _entries.length; + bool get ready => _ready; + double get progress => length / total(); final DateFormat formatter = DateFormat('yyyy-MM-dd'); // Constructor DatabaseAPI() { init(); - getEntries(); + getAll(); } init() { @@ -34,28 +37,67 @@ class DatabaseAPI extends ChangeNotifier { databases = Databases(client); } - getEntries({int limit = 100, String dateISO = ""}) async { - if (dateISO == "") { - dateISO = DateTime.now().toIso8601String(); - } + int total() { + String dateISO = DateTime.now().toIso8601String(); var referenceDate = DateTime.parse("2023-01-01"); final date = DateTime.parse(dateISO); - final offset = date.difference(referenceDate).inDays; + return date.difference(referenceDate).inDays; + } - print("Getting $limit entries starting from $offset"); + getAll() async { + String paginationQuery = Query.offset(0); + int max = total(); + while (_entries.length < max) { + int remainder = max - _entries.length; + int limit = remainder > 100 ? 100 : remainder; + + if (_entries.isNotEmpty) { + String lastDate = _entries.last.$id; + paginationQuery = Query.cursorAfter(lastDate); + } + + List newEntries = await getEntries( + limit: limit, + paginationQuery: paginationQuery, + ); + + _entries.addAll(newEntries); + notifyListeners(); + } + _ready = true; + } + + getOne({required String date}) async { + int offset = + DateTime.parse(date).difference(DateTime.parse("2023-01-01")).inDays + + 1; + + List response = await getEntries( + limit: 1, + paginationQuery: Query.offset(offset), + ); + + _entries.add(response.first); + _entries.sort( + (a, b) => a.$id.compareTo(b.$id), + ); + notifyListeners(); + return response.first; + } + + getEntries({int limit = 100, paginationQuery}) async { var response = await databases.listDocuments( databaseId: APPWRITE_DATABASE_ID, collectionId: COLLECTION, queries: [ Query.orderAsc("date"), - Query.offset(offset), + paginationQuery, Query.limit(limit), ]); - _entries = response.documents; - notifyListeners(); + return response.documents; } Future addEntry( diff --git a/ltx_flutter/lib/pages/_old_today_page.dart b/ltx_flutter/lib/pages/_old_today_page.dart new file mode 100644 index 0000000..622e947 --- /dev/null +++ b/ltx_flutter/lib/pages/_old_today_page.dart @@ -0,0 +1,132 @@ +import 'dart:math' as math; +import 'package:ltx_flutter/appwrite/auth_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:intl/intl.dart'; + +class TodayPage extends StatefulWidget { + const TodayPage({Key? key}) : super(key: key); + + @override + _TodayPageState createState() => _TodayPageState(); +} + +class _TodayPageState extends State { + final database = DatabaseAPI(); + AuthStatus authStatus = AuthStatus.uninitialized; + DateTime? date; + Document? entry; + + var hours = List.generate(24, (i) => i).map((e) { + var meridien = "AM"; + var hour = 12; + if (e > 12) { + hour = e - 12; + } else if (e > 0) { + hour = e; + } + if (e > 11) { + meridien = "PM"; + } + return { + "string": "${hour.toString()} $meridien", + "index": e.toInt(), + }; + }).toList(); + + @override + void initState() { + super.initState(); + final AuthAPI appwrite = context.read(); + authStatus = appwrite.status; + loadEntry(); + } + + loadEntry() async { + try { + final value = await database.getEntries(limit: 1, paginationQuery: ""); + setState(() { + entry = value.documents[0]; + }); + } catch (e) { + print(e); + } + } + + String formatDate({String format = "", String? dateISO}) { + final DateFormat dateFormatter = DateFormat(format); + final date = dateISO!.isEmpty ? DateTime.now() : DateTime.parse(dateISO); + return dateFormatter.format(date); + } + + @override + Widget build(BuildContext context) { + return Center( + child: entry == null + ? CircularProgressIndicator() + : Column( + children: [ + Text( + style: TextStyle( + fontSize: 40, + ), + formatDate( + format: "E, MMM dd, yyyy", + dateISO: entry?.data['date'], + ), + ), + Expanded( + child: ListView( + children: hours.map( + (e) { + int h = e["index"] as int; + List hoursData = entry!.data['hours']; + String hourData = (h >= hoursData.length) + ? "" + : hoursData[h].toString(); + return ListTile( + leading: Text(e["string"] as String), + title: Text(hourData), + ); + }, + ).toList()), + ), + Container( + padding: EdgeInsets.only(top: 10), + color: Colors.black, + child: Row( + children: List.generate(10, (i) => i).map( + (e) { + var generatedColor = + math.Random().nextInt(Colors.primaries.length); + return Expanded( + child: Padding( + padding: const EdgeInsets.all(2.0), + child: FilledButton( + style: ButtonStyle( + shape: MaterialStateProperty.all< + RoundedRectangleBorder>( + RoundedRectangleBorder( + borderRadius: BorderRadius.zero, + side: BorderSide(color: Colors.white))), + backgroundColor: MaterialStateProperty.all( + Colors.primaries[generatedColor]), + ), + onLongPress: () => + print("Long pressed ${e.toString()}"), + onPressed: () => print("Tapped ${e.toString()}"), + child: Text(e.toString()), + ), + ), + ); + }, + ).toList(), + ), + ), + ], + ), + ); + } +} diff --git a/ltx_flutter/lib/pages/account_page.dart b/ltx_flutter/lib/pages/account_page.dart index 0770f58..adb0a03 100644 --- a/ltx_flutter/lib/pages/account_page.dart +++ b/ltx_flutter/lib/pages/account_page.dart @@ -32,7 +32,7 @@ class _AccountPageState extends State { return ListTile( leading: Icon(Icons.edit_note), title: Text("Entries"), - trailing: Text("${entries.total}"), + trailing: Text("${entries.total()}"), ); }), Consumer(builder: (context, categories, child) { diff --git a/ltx_flutter/lib/pages/tabs_page.dart b/ltx_flutter/lib/pages/tabs_page.dart index b6507b3..608f608 100644 --- a/ltx_flutter/lib/pages/tabs_page.dart +++ b/ltx_flutter/lib/pages/tabs_page.dart @@ -11,7 +11,7 @@ class TabsPage extends StatefulWidget { } class _TabsPageState extends State { - int _selectedIndex = 2; + int _selectedIndex = 0; static const _widgets = [TodayPage(), CategoriesPage(), AccountPage()]; diff --git a/ltx_flutter/lib/pages/today_page.dart b/ltx_flutter/lib/pages/today_page.dart index 33b5185..f1d8a0b 100644 --- a/ltx_flutter/lib/pages/today_page.dart +++ b/ltx_flutter/lib/pages/today_page.dart @@ -1,10 +1,89 @@ -import 'dart:math' as math; -import 'package:ltx_flutter/appwrite/auth_api.dart'; +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:intl/intl.dart'; +import 'package:appwrite/models.dart'; + +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) { + var meridien = "AM"; + var hour = 12; + if (e > 12) { + hour = e - 12; + } else if (e > 0) { + hour = e; + } + if (e > 11) { + meridien = "PM"; + } + return "${hour.toString()} $meridien"; +} + +List generateHours(entry) { + if (entry == null) { + return [Center(child: RefreshProgressIndicator())]; + } + List hours = entry.data['hours']; + + // print(hours); + + List reduced = []; + int counter = 0; + 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++; + } + print(reduced); + + return reduced.map( + (e) { + double height = double.parse((e['num'] * 35).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( + children: [ + Text( + style: TextStyle(color: category.foregroundColor), + hourString(e['hour'])), + Expanded( + child: Center( + child: Text( + style: TextStyle(color: category.foregroundColor), + category.name), + ), + ), + ], + ), + ), + ), + ); + }, + ); + }, + ).toList(); +} class TodayPage extends StatefulWidget { const TodayPage({Key? key}) : super(key: key); @@ -14,119 +93,239 @@ class TodayPage extends StatefulWidget { } class _TodayPageState extends State { - final database = DatabaseAPI(); - AuthStatus authStatus = AuthStatus.uninitialized; - DateTime? date; - Document? entry; - - var hours = List.generate(24, (i) => i).map((e) { - var meridien = "AM"; - var hour = 12; - if (e > 12) { - hour = e - 12; - } else if (e > 0) { - hour = e; - } - if (e > 11) { - meridien = "PM"; - } - return { - "string": "${hour.toString()} $meridien", - "index": e.toInt(), - }; - }).toList(); - @override - void initState() { - super.initState(); - final AuthAPI appwrite = context.read(); - authStatus = appwrite.status; - loadEntry(); + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + if (constraints.maxWidth > 800) { + return Text("Wide"); + } else { + return Consumer(builder: (context, database, child) { + return database.ready + ? NarrowView() + : Center( + child: CircularProgressIndicator( + value: database.progress, + )); + }); + } + }); } +} - loadEntry() async { - try { - final value = await database.getEntries(limit: 1, dateISO: ""); - setState(() { - entry = value.documents[0]; - }); - } catch (e) { - print(e); - } - } - - String formatDate({String format = "", String? dateISO}) { - final DateFormat dateFormatter = DateFormat(format); - final date = dateISO!.isEmpty ? DateTime.now() : DateTime.parse(dateISO); - return dateFormatter.format(date); - } +class NarrowView extends StatelessWidget { + const NarrowView({ + super.key, + }); @override Widget build(BuildContext context) { - return Center( - child: entry == null - ? CircularProgressIndicator() - : Column( + return DefaultTabController( + length: 4, + initialIndex: 0, + child: Column( + children: [ + TabBar( + tabs: [ + Tab(icon: Icon(Icons.calendar_view_day)), + Tab(icon: Icon(Icons.calendar_view_week)), + Tab(icon: Icon(Icons.calendar_view_month)), + Tab(icon: Icon(Icons.all_inclusive)), + ], + ), + Expanded( + child: TabBarView( children: [ - Text( - style: TextStyle( - fontSize: 40, - ), - formatDate( - format: "E, MMM dd, yyyy", - dateISO: entry?.data['date'], - ), - ), - Expanded( - child: ListView( - children: hours.map( - (e) { - int h = e["index"] as int; - List hoursData = entry!.data['hours']; - String hourData = (h >= hoursData.length) - ? "" - : hoursData[h].toString(); - return ListTile( - leading: Text(e["string"] as String), - title: Text(hourData), - ); - }, - ).toList()), - ), - Container( - padding: EdgeInsets.only(top: 10), - color: Colors.black, - child: Row( - children: List.generate(10, (i) => i).map( - (e) { - var generatedColor = - math.Random().nextInt(Colors.primaries.length); - return Expanded( - child: Padding( - padding: const EdgeInsets.all(2.0), - child: FilledButton( - style: ButtonStyle( - shape: MaterialStateProperty.all< - RoundedRectangleBorder>( - RoundedRectangleBorder( - borderRadius: BorderRadius.zero, - side: BorderSide(color: Colors.white))), - backgroundColor: MaterialStateProperty.all( - Colors.primaries[generatedColor]), - ), - onLongPress: () => - print("Long pressed ${e.toString()}"), - onPressed: () => print("Tapped ${e.toString()}"), - child: Text(e.toString()), - ), - ), - ); - }, - ).toList(), - ), - ), + DayView(), + Icon(Icons.directions_transit, size: 350), + Icon(Icons.directions_car, size: 350), + Icon(Icons.abc), ], ), + ) + ], + ), + ); + } +} + +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 = ""; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + database = context.watch(); + categories = context.watch(); + print(categories.lookUp(9)); + entries = database.entries; + } + + void _incrementDate(int amount) => + _setDate(_date.add(Duration(days: amount))); + + void _setDate(DateTime? day) { + if (day != null) { + setState(() { + _date = day; + }); + } + } + + // Document? grabEntry(DateTime date) { + // DatabaseAPI database = context.watch(); + // entries = database.entries; + // String formattedDate = formatDate( + // dateISO: date.toIso8601String(), + // format: "yyyy-MM-dd", + // ); + + // try { + // dayEntry = entries.singleWhere((element) => element.$id == formattedDate); + // return dayEntry; + // } catch (e) { + // database.getOne(date: formattedDate).then((value) => dayEntry = value); + // } + // } + + @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_rate_outlined, + color: Colors.black, + ) + : Text( + style: TextStyle(fontSize: 20, color: Colors.black), + mood.toString()); + + return Column( + children: [ + SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.all(8.0), + 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: ListView( + children: categories.ready + ? generateHours(dayEntry) + : [CircularProgressIndicator()], + ), + ), + Padding( + padding: const EdgeInsets.only(top: 30, right: 30, left: 10), + child: Row( + children: [ + SizedBox(width: 10), + SizedBox.square( + dimension: 50, + child: Container( + color: Colors.amber, child: Center(child: moodWidget)), + ), + SizedBox(width: 30), + Expanded( + child: TextFormField( + decoration: InputDecoration(hintText: "Comments"), + smartQuotesType: SmartQuotesType.enabled, + enableInteractiveSelection: true, + controller: commentsController, + ), + ), + SizedBox(width: 30), + IconButton( + tooltip: "Save", + onPressed: () => print("Save"), + icon: Icon(Icons.save), + ) + ], + ), + ), + SizedBox(height: 30), + ], ); } }