Bug fixes; Infinitypage; Android app
This commit is contained in:
parent
0ea14e9c02
commit
66028b831d
@ -1,4 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<application
|
<application
|
||||||
android:label="ltx_flutter"
|
android:label="ltx_flutter"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|||||||
@ -122,6 +122,7 @@ class CategoriesAPI extends ChangeNotifier {
|
|||||||
Query.orderAsc("number"),
|
Query.orderAsc("number"),
|
||||||
]);
|
]);
|
||||||
_categories = response.documents;
|
_categories = response.documents;
|
||||||
|
_categories.removeWhere((element) => element.data['number'] == -1);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_ready = true;
|
_ready = true;
|
||||||
}
|
}
|
||||||
|
|||||||
64
ltx_flutter/lib/helpers.dart
Normal file
64
ltx_flutter/lib/helpers.dart
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,62 +6,9 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:appwrite/models.dart';
|
import 'package:appwrite/models.dart';
|
||||||
import 'package:timezone/timezone.dart' as tz;
|
import 'package:timezone/timezone.dart' as tz;
|
||||||
import 'package:timezone/data/latest.dart' as tz;
|
import 'package:timezone/data/latest.dart' as tz;
|
||||||
|
import 'today_views/day_view.dart';
|
||||||
String formatDate({String format = "", String? dateISO}) {
|
import 'today_views/infinity_view.dart';
|
||||||
final DateFormat dateFormatter = DateFormat(format);
|
import '../helpers.dart';
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TodayPage extends StatefulWidget {
|
class TodayPage extends StatefulWidget {
|
||||||
const TodayPage({Key? key}) : super(key: key);
|
const TodayPage({Key? key}) : super(key: key);
|
||||||
@ -113,11 +60,12 @@ class NarrowView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
children: [
|
children: [
|
||||||
DayView(),
|
DayView(),
|
||||||
Icon(Icons.directions_transit, size: 350),
|
Icon(Icons.directions_transit, size: 350),
|
||||||
Icon(Icons.directions_car, 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<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 = "";
|
|
||||||
|
|
||||||
bool _editable = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
database = context.watch<DatabaseAPI>();
|
|
||||||
categories = context.watch<CategoriesAPI>();
|
|
||||||
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<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_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<HourGenerator> createState() => _HourGeneratorState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _HourGeneratorState extends State<HourGenerator> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
List<Widget> generateHours(entry, bool edit) {
|
|
||||||
if (entry == null) {
|
|
||||||
return [Center(child: RefreshProgressIndicator())];
|
|
||||||
}
|
|
||||||
List<dynamic> 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<Widget> hourWidgets = reduced.map(
|
|
||||||
(e) {
|
|
||||||
double height = double.parse((e['num'] * 36).toString());
|
|
||||||
return Consumer<CategoriesAPI>(
|
|
||||||
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<Consumer<CategoriesAPI>> remainingHours =
|
|
||||||
List.generate(24 - counter, (index) {
|
|
||||||
return Consumer<CategoriesAPI>(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<Consumer<CategoriesAPI>> remainingHoursConsolidated = [
|
|
||||||
Consumer<CategoriesAPI>(
|
|
||||||
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<HourFormField> createState() => _HourFormFieldState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _HourFormFieldState extends State<HourFormField> {
|
|
||||||
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<DatabaseAPI>(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),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
528
ltx_flutter/lib/pages/today_views/day_view.dart
Normal file
528
ltx_flutter/lib/pages/today_views/day_view.dart
Normal file
@ -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<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 = "";
|
||||||
|
|
||||||
|
bool _editable = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
database = context.watch<DatabaseAPI>();
|
||||||
|
categories = context.watch<CategoriesAPI>();
|
||||||
|
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<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_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<HourGenerator> createState() => _HourGeneratorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HourGeneratorState extends State<HourGenerator> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<Widget> generateHours(entry, bool edit) {
|
||||||
|
if (entry == null) {
|
||||||
|
return [Center(child: RefreshProgressIndicator())];
|
||||||
|
}
|
||||||
|
List<dynamic> 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<Widget> hourWidgets = reduced.map(
|
||||||
|
(e) {
|
||||||
|
double height = double.parse((e['num'] * 36).toString());
|
||||||
|
return Consumer<CategoriesAPI>(
|
||||||
|
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<Consumer<CategoriesAPI>> remainingHours =
|
||||||
|
List.generate(24 - counter, (index) {
|
||||||
|
return Consumer<CategoriesAPI>(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<Consumer<CategoriesAPI>> remainingHoursConsolidated = [
|
||||||
|
Consumer<CategoriesAPI>(
|
||||||
|
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<HourFormField> createState() => _HourFormFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HourFormFieldState extends State<HourFormField> {
|
||||||
|
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<DatabaseAPI>(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),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
ltx_flutter/lib/pages/today_views/infinity_view.dart
Normal file
103
ltx_flutter/lib/pages/today_views/infinity_view.dart
Normal file
@ -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<InfinityView> createState() => _InfinityViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InfinityViewState extends State<InfinityView> {
|
||||||
|
DateTime _date = DateTime.now();
|
||||||
|
|
||||||
|
late DatabaseAPI database;
|
||||||
|
late CategoriesAPI categories;
|
||||||
|
|
||||||
|
late List<Document> entries = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
database = context.watch<DatabaseAPI>();
|
||||||
|
categories = context.watch<CategoriesAPI>();
|
||||||
|
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())),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user