First commit for ltx-flutter

This commit is contained in:
2023-05-20 13:55:28 -07:00
parent e961e9e6a5
commit 98cfbd578d
135 changed files with 5185 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
import 'package:appwrite/appwrite.dart';
import 'package:appwrite/models.dart';
import 'package:ltx_flutter/constants/constants.dart';
import 'package:flutter/widgets.dart';
enum AuthStatus {
uninitialized,
authenticated,
unauthenticated,
}
class AuthAPI extends ChangeNotifier {
Client client = Client();
late final Account account;
late User _currentUser;
AuthStatus _status = AuthStatus.uninitialized;
// Getter methods
User get currentUser => _currentUser;
AuthStatus get status => _status;
String? get username => _currentUser.name;
String? get email => _currentUser.email;
String? get userid => _currentUser.$id;
// Constructor
AuthAPI() {
init();
loadUser();
}
// Initialize the Appwrite client
init() {
client.setEndpoint(APPWRITE_URL).setProject(APPWRITE_PROJECT_ID);
account = Account(client);
}
loadUser() async {
try {
final user = await account.get();
_status = AuthStatus.authenticated;
_currentUser = user;
notifyListeners();
} catch (e) {
print(e);
_status = AuthStatus.unauthenticated;
notifyListeners();
}
}
Future<User> createUser(
{required String email, required String password}) async {
notifyListeners();
try {
final user = await account.create(
userId: ID.unique(),
email: email,
password: password,
name: 'Simon G');
return user;
} finally {
notifyListeners();
}
}
Future<Session> createEmailSession(
{required String email, required String password}) async {
notifyListeners();
try {
final session =
await account.createEmailSession(email: email, password: password);
_currentUser = await account.get();
_status = AuthStatus.authenticated;
return session;
} finally {
notifyListeners();
}
}
signInWithProvider({required String provider}) async {
try {
final session = await account.createOAuth2Session(provider: provider);
_currentUser = await account.get();
_status = AuthStatus.authenticated;
return session;
} finally {
notifyListeners();
}
}
signOut() async {
try {
await account.deleteSession(sessionId: 'current');
_status = AuthStatus.unauthenticated;
} finally {
notifyListeners();
}
}
Future<Preferences> getUserPreferences() async {
return await account.getPrefs();
}
updatePreferences({required String bio}) async {
return account.updatePrefs(prefs: {'bio': bio});
}
}
@@ -0,0 +1,71 @@
import 'package:appwrite/appwrite.dart';
import 'package:appwrite/models.dart';
import 'package:ltx_flutter/appwrite/appwrite.dart';
import 'package:ltx_flutter/constants/constants.dart';
import 'package:intl/intl.dart';
class DatabaseAPI {
Client client = Client();
late final Account account;
late final Databases databases;
final AuthAPI auth = AuthAPI();
final DateFormat formatter = DateFormat('yyyy-MM-dd');
DatabaseAPI() {
init();
}
init() {
client
.setEndpoint(APPWRITE_URL)
.setProject(APPWRITE_PROJECT_ID)
.setSelfSigned();
account = Account(client);
databases = Databases(client);
}
Future<DocumentList> getEntries({int limit = 100, String dateISO = ""}) {
if (dateISO == "") {
dateISO = formatter.format(DateTime.now());
}
final date = DateTime.parse(dateISO);
final offset = date.difference(DateTime.now()).inDays;
print("Getting ${limit} entries starting from ${offset}");
return databases.listDocuments(
databaseId: APPWRITE_DATABASE_ID,
collectionId: COLLECTION,
queries: [
Query.orderAsc("date"),
Query.offset(offset),
Query.limit(limit),
]);
}
Future<Document> addEntry(
{required String date,
List hours = const [],
int mood = -1,
String comments = ""}) {
return databases.createDocument(
databaseId: APPWRITE_DATABASE_ID,
collectionId: COLLECTION,
documentId: date,
data: {
'date': DateTime.parse(date),
'hours': hours,
'mood': mood,
'comments': comments
});
}
Future<dynamic> deleteEntry({required String date}) {
return databases.deleteDocument(
databaseId: APPWRITE_DATABASE_ID,
collectionId: COLLECTION,
documentId: date);
}
}
+4
View File
@@ -0,0 +1,4 @@
const String APPWRITE_PROJECT_ID = "lifetracker";
const String APPWRITE_DATABASE_ID = "lifetracker-db";
const String APPWRITE_URL = "https://db.ryanpandya.com/v1";
const String COLLECTION = "ryan";
+1
View File
@@ -0,0 +1 @@
+142
View File
@@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:ltx_flutter/pages/tabs_page.dart';
import 'package:ltx_flutter/appwrite/appwrite.dart';
import 'package:ltx_flutter/pages/login_page.dart';
import 'package:provider/provider.dart';
void main() {
runApp(ChangeNotifierProvider(
create: (context) => AuthAPI(),
child: LifetrackerApp(),
));
}
class LifetrackerApp extends StatelessWidget {
final TextStyle baseStyle = TextStyle(fontFamily: "monospace");
@override
Widget build(BuildContext context) {
final value = context.watch<AuthAPI>().status;
print('TOP CHANGE Value changed to: $value!');
return MaterialApp(
title: 'Lifetracker',
theme: ThemeData.from(
colorScheme: ColorScheme.dark(),
),
home: value == AuthStatus.uninitialized
? const Scaffold(
body: Center(child: CircularProgressIndicator()),
)
: value == AuthStatus.authenticated
? const TabsPage()
: LoginPage());
// DefaultTabController(
// length: 3,
// child: Scaffold(
// appBar: AppBar(
// title: Text("Lifetracker"),
// bottom: const TabBar(
// tabs: [
// Tab(icon: Icon(Icons.table_chart_sharp)),
// Tab(icon: Icon(Icons.account_circle_sharp)),
// Tab(icon: Icon(Icons.settings_backup_restore)),
// ],
// ),
// ),
// body: TabBarView(children: [
// TableOrPlaceholder(baseStyle: baseStyle),
// AccountPage(),
// Center(
// child: Text("Database settings"),
// ),
// ])),
// ));
// }
}
}
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),
],
)
],
),
),
);
}
}
+17
View File
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
class AccountPage extends StatefulWidget {
const AccountPage({Key? key}) : super(key: key);
@override
_AccountPageState createState() => _AccountPageState();
}
class _AccountPageState extends State<AccountPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text("Account")),
);
}
}
+116
View File
@@ -0,0 +1,116 @@
import 'package:appwrite/appwrite.dart';
import 'package:ltx_flutter/appwrite/appwrite.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final emailTextController = TextEditingController();
final passwordTextController = TextEditingController();
bool loading = false;
signIn() async {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: const [
CircularProgressIndicator(),
]),
);
});
try {
final AuthAPI appwrite = context.read<AuthAPI>();
await appwrite.createEmailSession(
email: emailTextController.text,
password: passwordTextController.text,
);
Navigator.pop(context);
} on AppwriteException catch (e) {
Navigator.pop(context);
showAlert(title: 'Login failed', text: e.message.toString());
}
}
showAlert({required String title, required String text}) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(title),
content: Text(text),
actions: [
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Ok'))
],
);
});
}
signInWithProvider(String provider) {
try {
context.read<AuthAPI>().signInWithProvider(provider: provider);
} on AppwriteException catch (e) {
showAlert(title: 'Login failed', text: e.message.toString());
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('LTX Android'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextField(
controller: emailTextController,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextField(
controller: passwordTextController,
decoration: const InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(),
),
obscureText: true,
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: () {
signIn();
},
icon: const Icon(Icons.login),
label: const Text("Sign in"),
),
],
),
),
),
);
}
}
+140
View File
@@ -0,0 +1,140 @@
import 'package:flutter/material.dart';
import 'package:spreadsheet_table/spreadsheet_table.dart';
import 'package:ltx_flutter/appwrite/appwrite.dart';
import 'package:ltx_flutter/appwrite/database_api.dart';
import 'package:provider/provider.dart';
import 'package:appwrite/models.dart';
import 'package:intl/intl.dart';
class TablePage extends StatefulWidget {
const TablePage({Key? key}) : super(key: key);
@override
_TablePageState createState() => _TablePageState();
}
class _TablePageState extends State<TablePage> {
final database = DatabaseAPI();
late List<Document>? entries = [];
AuthStatus authStatus = AuthStatus.uninitialized;
@override
void initState() {
super.initState();
final AuthAPI appwrite = context.read<AuthAPI>();
authStatus = appwrite.status;
loadEntries();
}
loadEntries() async {
try {
final value = await database.getEntries(limit: 5);
setState(() {
entries = value.documents;
});
} catch (e) {
print(e);
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10),
child: Column(
children: [
Expanded(
child: entries!.isEmpty
? Center(child: const CircularProgressIndicator())
: TableWidget(entries: entries)),
Expanded(child: DiagnosticsWidget(entries: entries)),
],
),
);
}
}
class DiagnosticsWidget extends StatelessWidget {
final List<Document>? entries;
const DiagnosticsWidget({
super.key,
this.entries,
});
@override
Widget build(BuildContext context) {
return ListView(
children: [
ListTile(
leading: Icon(Icons.date_range_outlined),
title: Text("Entries"),
subtitle: Text("${entries?.length}"),
),
],
);
}
}
class TableWidget extends StatelessWidget {
TableWidget({super.key, this.entries});
final List<Document>? entries;
final DateFormat dateFormatter = DateFormat('MM/dd');
final DateFormat dayFormatter = DateFormat('E');
@override
Widget build(BuildContext context) {
return SpreadsheetTable(
cellWidth: 42,
rowsCount: entries!.length,
colCount: 27,
cellBuilder: (_, int row, int col) {
var entry = entries?[row];
var date = DateTime.parse(entry?.data['date']);
var day = dayFormatter.format(date);
List hours = entry?.data['hours'];
if (col == 0) {
return Center(child: Text('$day'));
} else if (col > 0 && col < 25) {
return Center(child: Text(hours[col - 1].toString()));
} else {
return Center(child: Text(''));
}
},
legendBuilder: (_) => Center(child: Text("Date".toUpperCase())),
colHeaderBuilder: (_, index) {
var cellText = "";
if (index == 0) {
cellText = "Day";
} else if (index == 1) {
cellText = "12 AM";
} else if (index > 1 && index < 25) {
var meridien = "AM";
var hour = index - 1;
if (index > 12) {
meridien = "PM";
}
if (index > 13) {
hour = hour - 12;
}
cellText = "${hour.toString()} $meridien";
} else if (index == 25) {
cellText = "Notes";
} else if (index == 26) {
cellText = "Comments";
}
return Center(child: Text(cellText.toUpperCase()));
},
rowHeaderBuilder: (_, index) {
var date = DateTime.parse(entries![index].data["date"]);
var formattedDate = dateFormatter.format(date);
return Center(child: Text(formattedDate));
},
);
}
}
+42
View File
@@ -0,0 +1,42 @@
import 'package:ltx_flutter/pages/table_page.dart';
import 'package:ltx_flutter/pages/account_page.dart';
import 'package:flutter/material.dart';
class TabsPage extends StatefulWidget {
const TabsPage({Key? key}) : super(key: key);
@override
_TabsPageState createState() => _TabsPageState();
}
class _TabsPageState extends State<TabsPage> {
int _selectedIndex = 0;
static const _widgets = [TablePage(), AccountPage()];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Lifetracker"),
),
body: _widgets.elementAt(_selectedIndex),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.table_chart_outlined), label: "Tracker"),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle_outlined), label: "Account")
],
currentIndex: _selectedIndex,
onTap: _onItemTapped,
),
);
}
}