Compare commits
3 Commits
be43a1d6e9
...
0dd56cfaa6
| Author | SHA1 | Date | |
|---|---|---|---|
| 0dd56cfaa6 | |||
| 162f2469f8 | |||
| 77e4adc2ad |
71
ltx_flutter/lib/appwrite/categories_api.dart
Normal file
71
ltx_flutter/lib/appwrite/categories_api.dart
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import 'package:appwrite/appwrite.dart';
|
||||||
|
import 'package:appwrite/models.dart';
|
||||||
|
import 'package:ltx_flutter/constants/constants.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:ltx_flutter/appwrite/appwrite.dart';
|
||||||
|
|
||||||
|
class CategoriesAPI extends ChangeNotifier {
|
||||||
|
Client client = Client();
|
||||||
|
late final Account account;
|
||||||
|
late final Databases databases;
|
||||||
|
late final String userId;
|
||||||
|
final AuthAPI auth = AuthAPI();
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
CategoriesAPI() {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadUser() async {
|
||||||
|
// try {
|
||||||
|
// user = await account.get();
|
||||||
|
// notifyListeners();
|
||||||
|
// } catch (e) {
|
||||||
|
// print(e);
|
||||||
|
// notifyListeners();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
init() {
|
||||||
|
client.setEndpoint(APPWRITE_URL).setProject(APPWRITE_PROJECT_ID);
|
||||||
|
account = Account(client);
|
||||||
|
databases = Databases(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<DocumentList> getCategories() {
|
||||||
|
print("Getting categories");
|
||||||
|
return databases.listDocuments(
|
||||||
|
databaseId: CATEGORIES_DB,
|
||||||
|
collectionId: COLLECTION,
|
||||||
|
queries: [
|
||||||
|
Query.orderAsc("number"),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Document> addCategory({
|
||||||
|
required String name,
|
||||||
|
required double number,
|
||||||
|
required String color,
|
||||||
|
String? description,
|
||||||
|
int? parentId,
|
||||||
|
}) {
|
||||||
|
return databases.createDocument(
|
||||||
|
databaseId: CATEGORIES_DB,
|
||||||
|
collectionId: COLLECTION,
|
||||||
|
documentId: "category-${number.toString()}",
|
||||||
|
data: {
|
||||||
|
'name': name,
|
||||||
|
'number': number,
|
||||||
|
'color': color,
|
||||||
|
'parent': parentId,
|
||||||
|
'description': description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> deleteCategory({required double number}) {
|
||||||
|
return databases.deleteDocument(
|
||||||
|
databaseId: CATEGORIES_DB,
|
||||||
|
collectionId: COLLECTION,
|
||||||
|
documentId: "category-${number.toString()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,13 +27,15 @@ class DatabaseAPI {
|
|||||||
|
|
||||||
Future<DocumentList> getEntries({int limit = 100, String dateISO = ""}) {
|
Future<DocumentList> getEntries({int limit = 100, String dateISO = ""}) {
|
||||||
if (dateISO == "") {
|
if (dateISO == "") {
|
||||||
dateISO = formatter.format(DateTime.now());
|
dateISO = DateTime.now().toIso8601String();
|
||||||
}
|
}
|
||||||
|
|
||||||
final date = DateTime.parse(dateISO);
|
var referenceDate = DateTime.parse("2023-01-01");
|
||||||
final offset = date.difference(DateTime.now()).inDays;
|
|
||||||
|
|
||||||
print("Getting ${limit} entries starting from ${offset}");
|
final date = DateTime.parse(dateISO);
|
||||||
|
final offset = date.difference(referenceDate).inDays;
|
||||||
|
|
||||||
|
print("Getting $limit entries starting from $offset");
|
||||||
|
|
||||||
return databases.listDocuments(
|
return databases.listDocuments(
|
||||||
databaseId: APPWRITE_DATABASE_ID,
|
databaseId: APPWRITE_DATABASE_ID,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
const String APPWRITE_PROJECT_ID = "lifetracker";
|
const String APPWRITE_PROJECT_ID = "lifetracker";
|
||||||
const String APPWRITE_DATABASE_ID = "lifetracker-db";
|
const String APPWRITE_DATABASE_ID = "lifetracker-db";
|
||||||
|
const String CATEGORIES_DB = "categories";
|
||||||
const String APPWRITE_URL = "https://db.ryanpandya.com/v1";
|
const String APPWRITE_URL = "https://db.ryanpandya.com/v1";
|
||||||
const String COLLECTION = "ryan";
|
const String COLLECTION = "ryan";
|
||||||
|
|||||||
64
ltx_flutter/lib/pages/categories_page.dart
Normal file
64
ltx_flutter/lib/pages/categories_page.dart
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import 'package:ltx_flutter/appwrite/categories_api.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:appwrite/models.dart';
|
||||||
|
|
||||||
|
class CategoriesPage extends StatefulWidget {
|
||||||
|
const CategoriesPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CategoriesPageState createState() => _CategoriesPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CategoriesPageState extends State<CategoriesPage> {
|
||||||
|
final api = CategoriesAPI();
|
||||||
|
late List<Document>? categories = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loadCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future loadCategories() async {
|
||||||
|
try {
|
||||||
|
final value = await api.getCategories();
|
||||||
|
setState(() {
|
||||||
|
categories = value.documents;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: loadCategories,
|
||||||
|
child: Center(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: categories!.length + 1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index >= categories!.length || categories!.isEmpty) {
|
||||||
|
return ListTile(
|
||||||
|
leading: Text("New Category"),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Document? category = categories?[index];
|
||||||
|
Color backgroundColor =
|
||||||
|
Color(int.parse("0x${category!.data['color']}"));
|
||||||
|
Color textColor = backgroundColor.computeLuminance() > 0.2
|
||||||
|
? Colors.black
|
||||||
|
: Colors.white;
|
||||||
|
return ListTile(
|
||||||
|
tileColor: backgroundColor,
|
||||||
|
textColor: textColor,
|
||||||
|
leading: Text(category.data['number'].toString()),
|
||||||
|
title: Text(category.data['name']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,6 @@
|
|||||||
import 'package:appwrite/appwrite.dart';
|
import 'package:appwrite/appwrite.dart';
|
||||||
import 'package:ltx_flutter/appwrite/appwrite.dart';
|
import 'package:ltx_flutter/appwrite/appwrite.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
|
|||||||
@ -61,7 +61,7 @@ class _TablePageState extends State<TablePage> {
|
|||||||
title: hourTitle,
|
title: hourTitle,
|
||||||
field: "hours[$e]",
|
field: "hours[$e]",
|
||||||
type: PlutoColumnType.number(),
|
type: PlutoColumnType.number(),
|
||||||
width: 65,
|
width: 70,
|
||||||
enableContextMenu: false,
|
enableContextMenu: false,
|
||||||
enableDropToResize: false,
|
enableDropToResize: false,
|
||||||
textAlign: PlutoColumnTextAlign.center,
|
textAlign: PlutoColumnTextAlign.center,
|
||||||
@ -75,23 +75,22 @@ class _TablePageState extends State<TablePage> {
|
|||||||
field: 'date',
|
field: 'date',
|
||||||
type: PlutoColumnType.text(),
|
type: PlutoColumnType.text(),
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
width: 110,
|
width: 70,
|
||||||
frozen: PlutoColumnFrozen.start,
|
|
||||||
enableContextMenu: false,
|
enableContextMenu: false,
|
||||||
enableDropToResize: false,
|
enableDropToResize: false,
|
||||||
textAlign: PlutoColumnTextAlign.center,
|
textAlign: PlutoColumnTextAlign.center,
|
||||||
titleTextAlign: PlutoColumnTextAlign.center,
|
titleTextAlign: PlutoColumnTextAlign.center,
|
||||||
|
frozen: PlutoColumnFrozen.start,
|
||||||
|
),
|
||||||
|
PlutoColumn(
|
||||||
|
title: 'DAY',
|
||||||
|
field: 'day',
|
||||||
|
type: PlutoColumnType.text(),
|
||||||
|
readOnly: true,
|
||||||
|
width: 53,
|
||||||
|
enableContextMenu: false,
|
||||||
|
enableDropToResize: false,
|
||||||
),
|
),
|
||||||
// PlutoColumn(
|
|
||||||
// title: 'DAY',
|
|
||||||
// field: 'day',
|
|
||||||
// type: PlutoColumnType.text(),
|
|
||||||
// readOnly: true,
|
|
||||||
// width: 50,
|
|
||||||
// frozen: PlutoColumnFrozen.start,
|
|
||||||
// enableContextMenu: false,
|
|
||||||
// enableDropToResize: false,
|
|
||||||
// ),
|
|
||||||
] +
|
] +
|
||||||
hourCols +
|
hourCols +
|
||||||
[
|
[
|
||||||
@ -117,23 +116,27 @@ class _TablePageState extends State<TablePage> {
|
|||||||
var cells = {
|
var cells = {
|
||||||
'date': PlutoCell(
|
'date': PlutoCell(
|
||||||
value: formatDate(
|
value: formatDate(
|
||||||
format: "MM/DD E",
|
format: "MM/DD",
|
||||||
|
dateISO: e.data['date'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'day': PlutoCell(
|
||||||
|
value: formatDate(
|
||||||
|
format: "E",
|
||||||
dateISO: e.data['date'],
|
dateISO: e.data['date'],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 'day': PlutoCell(
|
|
||||||
// value: formatDate(
|
|
||||||
// format: "E",
|
|
||||||
// dateISO: e.data['date'],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
'mood': PlutoCell(value: e.data['mood']),
|
'mood': PlutoCell(value: e.data['mood']),
|
||||||
'comments': PlutoCell(value: e.data['comments']),
|
'comments': PlutoCell(value: e.data['comments']),
|
||||||
};
|
};
|
||||||
var hours = <String, PlutoCell>{};
|
var hours = <String, PlutoCell>{};
|
||||||
for (int i = 0; i < 24; i++) {
|
for (int i = 0; i < 24; i++) {
|
||||||
hours.putIfAbsent(
|
hours.putIfAbsent(
|
||||||
"hours[$i]", () => PlutoCell(value: e.data['hours'][i]));
|
"hours[$i]",
|
||||||
|
() => PlutoCell(
|
||||||
|
value: e.data['hours'][i],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
cells.addEntries(hours.entries);
|
cells.addEntries(hours.entries);
|
||||||
|
|
||||||
@ -159,8 +162,7 @@ class _TablePageState extends State<TablePage> {
|
|||||||
child: entries!.isEmpty
|
child: entries!.isEmpty
|
||||||
? Center(child: const CircularProgressIndicator())
|
? Center(child: const CircularProgressIndicator())
|
||||||
: Scaffold(
|
: Scaffold(
|
||||||
body: Container(
|
body: InteractiveViewer(
|
||||||
padding: const EdgeInsets.all(0),
|
|
||||||
child: PlutoGrid(
|
child: PlutoGrid(
|
||||||
columns: columns,
|
columns: columns,
|
||||||
rows: rows,
|
rows: rows,
|
||||||
@ -252,7 +254,7 @@ class SpreadsheetWidget extends StatelessWidget {
|
|||||||
List hours = entry?.data['hours'];
|
List hours = entry?.data['hours'];
|
||||||
|
|
||||||
if (col == 0) {
|
if (col == 0) {
|
||||||
return Center(child: Text('$day'));
|
return Center(child: Text(day));
|
||||||
} else if (col > 0 && col < 25) {
|
} else if (col > 0 && col < 25) {
|
||||||
return Center(child: Text(hours[col - 1].toString()));
|
return Center(child: Text(hours[col - 1].toString()));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:ltx_flutter/pages/table_page.dart';
|
|
||||||
import 'package:ltx_flutter/pages/account_page.dart';
|
import 'package:ltx_flutter/pages/account_page.dart';
|
||||||
|
import 'package:ltx_flutter/pages/categories_page.dart';
|
||||||
|
import 'package:ltx_flutter/pages/today_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class TabsPage extends StatefulWidget {
|
class TabsPage extends StatefulWidget {
|
||||||
@ -12,7 +13,7 @@ class TabsPage extends StatefulWidget {
|
|||||||
class _TabsPageState extends State<TabsPage> {
|
class _TabsPageState extends State<TabsPage> {
|
||||||
int _selectedIndex = 0;
|
int _selectedIndex = 0;
|
||||||
|
|
||||||
static const _widgets = [TablePage(), AccountPage()];
|
static const _widgets = [TodayPage(), CategoriesPage(), AccountPage()];
|
||||||
|
|
||||||
void _onItemTapped(int index) {
|
void _onItemTapped(int index) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -24,13 +25,15 @@ class _TabsPageState extends State<TabsPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("Lifetracker"),
|
title: Text("LTX Android"),
|
||||||
),
|
),
|
||||||
body: _widgets.elementAt(_selectedIndex),
|
body: _widgets.elementAt(_selectedIndex),
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
items: const [
|
items: const [
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Icons.table_chart_outlined), label: "Tracker"),
|
icon: Icon(Icons.today_outlined), label: "Today"),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.category_outlined), label: "Categories"),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Icons.account_circle_outlined), label: "Account")
|
icon: Icon(Icons.account_circle_outlined), label: "Account")
|
||||||
],
|
],
|
||||||
|
|||||||
99
ltx_flutter/lib/pages/today_page.dart
Normal file
99
ltx_flutter/lib/pages/today_page.dart
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import 'package:ltx_flutter/appwrite/appwrite.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, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,7 @@ dependencies:
|
|||||||
flutter_svg: ^2.0.6
|
flutter_svg: ^2.0.6
|
||||||
intl: ^0.18.1
|
intl: ^0.18.1
|
||||||
pluto_grid: ^7.0.2
|
pluto_grid: ^7.0.2
|
||||||
|
string_to_hex: ^0.2.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user