First commit for ltx-flutter
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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")),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user