Categories page works!

This commit is contained in:
Ryan Pandya 2023-06-01 00:52:44 -04:00
parent 0dd56cfaa6
commit c9c3f69886
12 changed files with 428 additions and 171 deletions

View File

@ -1,30 +1,63 @@
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';
import 'package:ltx_flutter/constants/colors.dart';
import 'package:flutter/material.dart';
class Category {
late final Color backgroundColor;
late final Color foregroundColor;
late final num number;
late final num? parent;
late final String name;
late final String? description;
Category(Document doc) {
backgroundColor = _getBackgroundColor(doc.data['color']);
foregroundColor = _getForegroundColor(doc.data['color']);
number = doc.data['number'];
parent = doc.data['parent'];
name = doc.data['name'];
description = doc.data['description'];
}
Color _getBackgroundColor(String colorStr) {
return Color(int.parse("0xff$colorStr"));
}
Color _getForegroundColor(String colorStr) {
return Color(int.parse("0xff$colorStr")).computeLuminance() > 0.5
? Colors.black
: Colors.white;
}
bool hasParent() {
return parent == number ? false : true;
}
double leftPadding() {
return hasParent() ? 50.0 : 20.0;
}
}
class CategoriesAPI extends ChangeNotifier {
Client client = Client();
late final Account account;
late final Databases databases;
late final String userId;
final AuthAPI auth = AuthAPI();
late List<Document> _categories = [];
// Constructor
CategoriesAPI() {
init();
getCategories();
}
// loadUser() async {
// try {
// user = await account.get();
// notifyListeners();
// } catch (e) {
// print(e);
// notifyListeners();
// }
// }
// Getters
get total => _categories.length;
get all => _categories;
get isEmpty => _categories.isEmpty;
get(n) => Category(_categories[n]);
get colors => CategoryColor.values;
init() {
client.setEndpoint(APPWRITE_URL).setProject(APPWRITE_PROJECT_ID);
@ -32,34 +65,45 @@ class CategoriesAPI extends ChangeNotifier {
databases = Databases(client);
}
Future<DocumentList> getCategories() {
print("Getting categories");
return databases.listDocuments(
getCategories() async {
print("Updating categories.");
_categories = [];
var response = await databases.listDocuments(
databaseId: CATEGORIES_DB,
collectionId: COLLECTION,
queries: [
Query.orderAsc("parent"),
Query.orderAsc("number"),
]);
_categories = response.documents;
notifyListeners();
}
Future<Document> addCategory({
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,
});
}) async {
try {
return await databases.createDocument(
databaseId: CATEGORIES_DB,
collectionId: COLLECTION,
documentId: "category-${number.toString()}",
data: {
'name': name,
'number': number,
'color': color,
'parent': parentId,
'description': description,
});
} catch (e) {
print(e);
throw "Didn't work";
} finally {
notifyListeners();
}
}
Future<dynamic> deleteCategory({required double number}) {

View File

@ -1,19 +1,28 @@
import 'package:appwrite/appwrite.dart';
import 'package:appwrite/models.dart';
import 'package:ltx_flutter/appwrite/appwrite.dart';
import 'package:flutter/material.dart';
import 'package:ltx_flutter/appwrite/auth_api.dart';
import 'package:ltx_flutter/constants/constants.dart';
import 'package:intl/intl.dart';
class DatabaseAPI {
class DatabaseAPI extends ChangeNotifier {
Client client = Client();
late final Account account;
late final Databases databases;
final AuthAPI auth = AuthAPI();
late List<Document> _entries = [];
// Getter methods
List<Document> get entries => _entries;
int? get total => _entries.length;
final DateFormat formatter = DateFormat('yyyy-MM-dd');
// Constructor
DatabaseAPI() {
init();
getEntries();
}
init() {
@ -25,7 +34,7 @@ class DatabaseAPI {
databases = Databases(client);
}
Future<DocumentList> getEntries({int limit = 100, String dateISO = ""}) {
getEntries({int limit = 100, String dateISO = ""}) async {
if (dateISO == "") {
dateISO = DateTime.now().toIso8601String();
}
@ -37,7 +46,7 @@ class DatabaseAPI {
print("Getting $limit entries starting from $offset");
return databases.listDocuments(
var response = await databases.listDocuments(
databaseId: APPWRITE_DATABASE_ID,
collectionId: COLLECTION,
queries: [
@ -45,6 +54,8 @@ class DatabaseAPI {
Query.offset(offset),
Query.limit(limit),
]);
_entries = response.documents;
notifyListeners();
}
Future<Document> addEntry(

View File

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
enum CategoryColor {
black("273036"),
blue("00a9b3"),
red("c71634"),
darkred("ff2816"),
lime("bfff55"),
green("189749"),
pink("ff65ae"),
purple("5b3ab1"),
cyan("005744"),
orange("ff6d01"),
yellow("fff336");
const CategoryColor(this.hex);
final String hex;
name() {
return this.toString().split('.').last;
}
backgroundColor() {
return Color(int.parse("0xff$hex"));
}
foregroundColor() {
return Color(int.parse("0xff$hex")).computeLuminance() > 0.3
? Colors.black
: Colors.white;
}
}

View File

@ -1,12 +1,19 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:ltx_flutter/pages/tabs_page.dart';
import 'package:ltx_flutter/appwrite/appwrite.dart';
import 'package:ltx_flutter/appwrite/auth_api.dart';
import 'package:ltx_flutter/appwrite/database_api.dart';
import 'package:ltx_flutter/appwrite/categories_api.dart';
import 'package:ltx_flutter/pages/login_page.dart';
import 'package:provider/provider.dart';
void main() {
runApp(ChangeNotifierProvider(
create: (context) => AuthAPI(),
runApp(MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AuthAPI()),
ChangeNotifierProvider(create: (context) => DatabaseAPI()),
ChangeNotifierProvider(create: (context) => CategoriesAPI()),
],
child: LifetrackerApp(),
));
}
@ -16,19 +23,22 @@ class LifetrackerApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final value = context.watch<AuthAPI>().status;
print('TOP CHANGE Value changed to: $value!');
final loginStatus = context.watch<AuthAPI>().status;
return MaterialApp(
scrollBehavior: const MaterialScrollBehavior().copyWith(dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.trackpad,
}),
title: 'Lifetracker',
theme: ThemeData.from(
colorScheme: ColorScheme.dark(),
),
home: value == AuthStatus.uninitialized
home: loginStatus == AuthStatus.uninitialized
? const Scaffold(
body: Center(child: CircularProgressIndicator()),
)
: value == AuthStatus.authenticated
: loginStatus == AuthStatus.authenticated
? const TabsPage()
: LoginPage());
// DefaultTabController(
@ -55,88 +65,3 @@ class LifetrackerApp extends StatelessWidget {
// }
}
}
class AccountPage extends StatefulWidget {
const AccountPage({
super.key,
});
@override
AccountPageState createState() {
return AccountPageState();
}
}
class AccountPageState extends State<AccountPage> {
final _loginFormKey = GlobalKey<FormState>();
String email = "";
String password = "";
void _logIn(context) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Logging in...')),
);
// client.login(email, password);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(30.0),
child: Form(
key: _loginFormKey,
child: Column(
children: [
SizedBox(
width: 250,
child: TextFormField(
obscureText: false,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Email',
),
keyboardType: TextInputType.emailAddress,
onChanged: (value) => setState(() {
email = value;
}),
)),
SizedBox(
height: 20,
),
SizedBox(
width: 250,
child: TextFormField(
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
),
keyboardType: TextInputType.visiblePassword,
onChanged: (value) => setState(() {
password = value;
}),
)),
SizedBox(
height: 20,
),
ElevatedButton.icon(
onPressed: () {
_logIn(context);
},
icon: Icon(Icons.login),
label: Text("Log in")),
Column(
children: <Widget>[
email.isEmpty ? Text("No data") : Text(email),
SizedBox(
height: 10,
),
password.isEmpty ? Text("No Data") : Text(password),
],
)
],
),
),
);
}
}

View File

@ -1,4 +1,8 @@
import 'package:flutter/material.dart';
import 'package:ltx_flutter/appwrite/auth_api.dart';
import 'package:ltx_flutter/appwrite/database_api.dart';
import 'package:ltx_flutter/appwrite/categories_api.dart';
import 'package:provider/provider.dart';
class AccountPage extends StatefulWidget {
const AccountPage({Key? key}) : super(key: key);
@ -10,8 +14,47 @@ class AccountPage extends StatefulWidget {
class _AccountPageState extends State<AccountPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text("Account")),
return Column(
children: [
Expanded(
child: ListView(
children: [
Card(
child: Consumer<AuthAPI>(builder: (context, account, child) {
return ListTile(
leading: Icon(Icons.person),
title: Text("Account"),
trailing: Text("${account.username}"),
);
}),
),
Card(
child:
Consumer<DatabaseAPI>(builder: (context, entries, child) {
return ListTile(
leading: Icon(Icons.edit_note),
title: Text("Entries"),
trailing: Text("${entries.total}"),
);
}),
),
Card(
child: Consumer<CategoriesAPI>(
builder: (context, categories, child) {
return ListTile(
leading: Icon(Icons.category),
title: Text("Categories"),
trailing: Text("${categories.total}"),
);
}),
),
],
),
),
Expanded(
child: Placeholder(),
),
],
);
}
}

View File

@ -1,6 +1,8 @@
import 'package:ltx_flutter/appwrite/categories_api.dart';
import 'package:flutter/material.dart';
import 'package:appwrite/models.dart';
import 'package:provider/provider.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
class CategoriesPage extends StatefulWidget {
const CategoriesPage({Key? key}) : super(key: key);
@ -10,53 +12,218 @@ class CategoriesPage extends StatefulWidget {
}
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']),
);
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => const NewCategoryPage()),
).then((value) {
setState(() {});
}),
tooltip: "New Category",
child: Icon(Icons.add),
),
body: Consumer<CategoriesAPI>(builder: (context, categories, child) {
return ListView.builder(
itemCount: categories.total + 1,
itemBuilder: (context, i) {
if (i < categories.total) {
Category category = categories.get(i);
if (!category.hasParent() && category.number != 0) {
return Column(
children: [
Divider(
thickness: 1,
),
CategoryRow(category: category),
],
);
}
return CategoryRow(category: category);
}
},
);
}),
);
}
}
class CategoryRow extends StatelessWidget {
const CategoryRow({
super.key,
required this.category,
});
final Category category;
@override
Widget build(BuildContext context) {
return Row(
children: [
Container(
color: category.backgroundColor,
child: SizedBox.square(
dimension: 70,
child: Center(
child: Text(
style: TextStyle(
fontWeight: FontWeight.bold,
color: category.foregroundColor,
fontSize: 18,
),
category.number.toString())),
),
),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: category.leftPadding()),
child: Text(
style: TextStyle(
fontSize: 17,
),
category.name,
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Text(
style: TextStyle(
fontSize: 13,
color: Colors.white38,
),
"${category.description}",
),
),
),
],
);
}
}
class NewCategoryPage extends StatefulWidget {
const NewCategoryPage({super.key});
@override
State<NewCategoryPage> createState() => _NewCategoryPageState();
}
class _NewCategoryPageState extends State<NewCategoryPage> {
String _name = "";
final _formKey = GlobalKey<FormBuilderState>();
@override
void initState() {
super.initState();
_name = "New Category";
}
@override
Widget build(BuildContext context) {
final CategoriesAPI api = context.watch<CategoriesAPI>();
saveCategory(form, context) {
var name = form?.fields['categoryName'].value;
var number = form?.fields['categoryNumber'].value;
var color = form?.fields['categoryColor'].value;
var description = form?.fields['categoryDescription'].value;
var parent = form?.fields['categoryParent'].value;
api.addCategory(
name: name,
number: double.parse(number),
color: color,
description: description,
parentId: int.parse(parent),
);
Navigator.pop(context);
api.getCategories();
}
return Scaffold(
appBar: AppBar(
title: const Text("New Category"),
),
body: Padding(
padding: const EdgeInsets.only(left: 50, right: 50),
child: Column(
children: [
FormBuilder(
key: _formKey,
child: Column(
children: [
SizedBox(height: 25),
FormBuilderTextField(
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: "Number",
),
name: 'categoryNumber',
),
SizedBox(height: 25),
FormBuilderTextField(
decoration: InputDecoration(
labelText: "Name",
),
name: 'categoryName',
),
SizedBox(height: 25),
Consumer<CategoriesAPI>(
builder: (context, categories, child) {
print(categories.colors);
return FormBuilderDropdown(
decoration: InputDecoration(
labelText: "Color",
),
items: [
for (var c in categories.colors)
DropdownMenuItem(
value: c.hex,
child: Container(
decoration:
BoxDecoration(color: c.backgroundColor()),
padding: EdgeInsets.all(20),
child: Text(
c.name(),
style: TextStyle(color: c.foregroundColor()),
),
),
)
],
name: 'categoryColor',
);
},
),
SizedBox(height: 25),
FormBuilderTextField(
name: 'categoryDescription',
decoration: InputDecoration(
labelText: "Description",
),
),
SizedBox(height: 25),
FormBuilderTextField(
keyboardType: TextInputType.number,
name: 'categoryParent',
decoration: InputDecoration(
labelText: "Parent",
),
),
],
),
),
SizedBox(height: 40),
OutlinedButton(
onPressed: () => saveCategory(_formKey.currentState, context),
child: Text("Save"),
),
],
),
),
);

View File

@ -1,5 +1,5 @@
import 'package:appwrite/appwrite.dart';
import 'package:ltx_flutter/appwrite/appwrite.dart';
import 'package:ltx_flutter/appwrite/auth_api.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:spreadsheet_table/spreadsheet_table.dart';
import 'package:pluto_grid/pluto_grid.dart';
import 'package:ltx_flutter/appwrite/appwrite.dart';
import 'package:ltx_flutter/appwrite/auth_api.dart';
import 'package:ltx_flutter/appwrite/database_api.dart';
import 'package:provider/provider.dart';
import 'package:appwrite/models.dart';

View File

@ -11,7 +11,7 @@ class TabsPage extends StatefulWidget {
}
class _TabsPageState extends State<TabsPage> {
int _selectedIndex = 0;
int _selectedIndex = 2;
static const _widgets = [TodayPage(), CategoriesPage(), AccountPage()];
@ -25,7 +25,7 @@ class _TabsPageState extends State<TabsPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("LTX Android"),
title: Text("Flutter"),
),
body: _widgets.elementAt(_selectedIndex),
bottomNavigationBar: BottomNavigationBar(

View File

@ -1,4 +1,5 @@
import 'package:ltx_flutter/appwrite/appwrite.dart';
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';
@ -92,6 +93,38 @@ class _TodayPageState extends State<TodayPage> {
},
).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

@ -18,6 +18,7 @@ dependencies:
intl: ^0.18.1
pluto_grid: ^7.0.2
string_to_hex: ^0.2.2
flutter_form_builder: ^9.0.0
dev_dependencies:
flutter_test: