Got a first draft of TodayPage working!

This commit is contained in:
Ryan Pandya 2023-06-03 00:21:09 -04:00
parent 4f7d3879a8
commit dfeb19126e
6 changed files with 506 additions and 120 deletions

View File

@ -79,6 +79,7 @@ class CategoriesAPI extends ChangeNotifier {
late final Account account; late final Account account;
late final Databases databases; late final Databases databases;
late List<Document> _categories = []; late List<Document> _categories = [];
bool _ready = false;
// Constructor // Constructor
CategoriesAPI() { CategoriesAPI() {
@ -92,6 +93,17 @@ class CategoriesAPI extends ChangeNotifier {
get isEmpty => _categories.isEmpty; get isEmpty => _categories.isEmpty;
get(n) => Category(_categories[n]); get(n) => Category(_categories[n]);
get colors => CategoryColor.values; 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() { init() {
client.setEndpoint(APPWRITE_URL).setProject(APPWRITE_PROJECT_ID); client.setEndpoint(APPWRITE_URL).setProject(APPWRITE_PROJECT_ID);
@ -112,6 +124,7 @@ class CategoriesAPI extends ChangeNotifier {
]); ]);
_categories = response.documents; _categories = response.documents;
notifyListeners(); notifyListeners();
_ready = true;
} }
Future<Document>? addCategory({ Future<Document>? addCategory({

View File

@ -12,17 +12,20 @@ class DatabaseAPI extends ChangeNotifier {
final AuthAPI auth = AuthAPI(); final AuthAPI auth = AuthAPI();
late List<Document> _entries = []; late List<Document> _entries = [];
bool _ready = false;
// Getter methods // Getter methods
List<Document> get entries => _entries; List<Document> 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'); final DateFormat formatter = DateFormat('yyyy-MM-dd');
// Constructor // Constructor
DatabaseAPI() { DatabaseAPI() {
init(); init();
getEntries(); getAll();
} }
init() { init() {
@ -34,28 +37,67 @@ class DatabaseAPI extends ChangeNotifier {
databases = Databases(client); databases = Databases(client);
} }
getEntries({int limit = 100, String dateISO = ""}) async { int total() {
if (dateISO == "") { String dateISO = DateTime.now().toIso8601String();
dateISO = DateTime.now().toIso8601String();
}
var referenceDate = DateTime.parse("2023-01-01"); var referenceDate = DateTime.parse("2023-01-01");
final date = DateTime.parse(dateISO); 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<Document> 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<Document> 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( var response = await databases.listDocuments(
databaseId: APPWRITE_DATABASE_ID, databaseId: APPWRITE_DATABASE_ID,
collectionId: COLLECTION, collectionId: COLLECTION,
queries: [ queries: [
Query.orderAsc("date"), Query.orderAsc("date"),
Query.offset(offset), paginationQuery,
Query.limit(limit), Query.limit(limit),
]); ]);
_entries = response.documents; return response.documents;
notifyListeners();
} }
Future<Document> addEntry( Future<Document> addEntry(

View File

@ -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<TodayPage> {
final database = DatabaseAPI();
AuthStatus authStatus = AuthStatus.uninitialized;
DateTime? date;
Document? entry;
var hours = List<int>.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<AuthAPI>();
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<dynamic> 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<int>.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(),
),
),
],
),
);
}
}

View File

@ -32,7 +32,7 @@ class _AccountPageState extends State<AccountPage> {
return ListTile( return ListTile(
leading: Icon(Icons.edit_note), leading: Icon(Icons.edit_note),
title: Text("Entries"), title: Text("Entries"),
trailing: Text("${entries.total}"), trailing: Text("${entries.total()}"),
); );
}), }),
Consumer<CategoriesAPI>(builder: (context, categories, child) { Consumer<CategoriesAPI>(builder: (context, categories, child) {

View File

@ -11,7 +11,7 @@ class TabsPage extends StatefulWidget {
} }
class _TabsPageState extends State<TabsPage> { class _TabsPageState extends State<TabsPage> {
int _selectedIndex = 2; int _selectedIndex = 0;
static const _widgets = [TodayPage(), CategoriesPage(), AccountPage()]; static const _widgets = [TodayPage(), CategoriesPage(), AccountPage()];

View File

@ -1,10 +1,89 @@
import 'dart:math' as math; import 'package:ltx_flutter/appwrite/categories_api.dart';
import 'package:ltx_flutter/appwrite/auth_api.dart';
import 'package:ltx_flutter/appwrite/database_api.dart'; import 'package:ltx_flutter/appwrite/database_api.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:appwrite/models.dart';
import 'package:intl/intl.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<Widget> generateHours(entry) {
if (entry == null) {
return [Center(child: RefreshProgressIndicator())];
}
List<dynamic> 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<CategoriesAPI>(
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 { class TodayPage extends StatefulWidget {
const TodayPage({Key? key}) : super(key: key); const TodayPage({Key? key}) : super(key: key);
@ -14,119 +93,239 @@ class TodayPage extends StatefulWidget {
} }
class _TodayPageState extends State<TodayPage> { class _TodayPageState extends State<TodayPage> {
final database = DatabaseAPI();
AuthStatus authStatus = AuthStatus.uninitialized;
DateTime? date;
Document? entry;
var hours = List<int>.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 @override
void initState() { Widget build(BuildContext context) {
super.initState(); return LayoutBuilder(
final AuthAPI appwrite = context.read<AuthAPI>(); builder: (BuildContext context, BoxConstraints constraints) {
authStatus = appwrite.status; if (constraints.maxWidth > 800) {
loadEntry(); return Text("Wide");
} else {
return Consumer<DatabaseAPI>(builder: (context, database, child) {
return database.ready
? NarrowView()
: Center(
child: CircularProgressIndicator(
value: database.progress,
));
});
}
});
} }
}
loadEntry() async { class NarrowView extends StatelessWidget {
try { const NarrowView({
final value = await database.getEntries(limit: 1, dateISO: ""); super.key,
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center( return DefaultTabController(
child: entry == null length: 4,
? CircularProgressIndicator() initialIndex: 0,
: Column( 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: [ children: [
Text( DayView(),
style: TextStyle( Icon(Icons.directions_transit, size: 350),
fontSize: 40, Icon(Icons.directions_car, size: 350),
), Icon(Icons.abc),
formatDate(
format: "E, MMM dd, yyyy",
dateISO: entry?.data['date'],
),
),
Expanded(
child: ListView(
children: hours.map(
(e) {
int h = e["index"] as int;
List<dynamic> 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<int>.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(),
),
),
], ],
), ),
)
],
),
);
}
}
class DayView extends StatefulWidget {
const DayView({
super.key,
});
@override
State<DayView> createState() => _DayViewState();
}
class _DayViewState extends State<DayView> {
DateTime _date = DateTime.now();
late DatabaseAPI database;
late CategoriesAPI categories;
late List<Document> entries = [];
late Document? dayEntry = null;
late List<num> hours = [];
late num? mood = 0;
late String comments = "";
@override
void didChangeDependencies() {
super.didChangeDependencies();
database = context.watch<DatabaseAPI>();
categories = context.watch<CategoriesAPI>();
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<DatabaseAPI>();
// 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<Document> 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),
],
); );
} }
} }