import 'dart:async'; import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/cupertino.dart'; import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tasktracker/NotificationsManager.dart'; import 'main.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'Data.dart'; import 'package:sqflite/sqflite.dart'; import 'package:path_provider/path_provider.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:sqflite_common/sqlite_api.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart' as SqlFF; import 'Tasks.dart'; late http.Response loginResponse; late var cacheDb; late String username; List categories = []; List taskTypes = []; List activities = []; bool offline = true; bool registered = false; StreamController refreshStream = StreamController(); Future login(String _username, String password) async { final prefs = await SharedPreferences.getInstance(); username = _username; var device_id = await Settings.UUID(); try { loginResponse = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/login.php'), body: {"username": _username, "password": password, "device_id": device_id})); if (loginResponse.body.toLowerCase().contains("success")) { offline = false; username = _username; registered = loginResponse.body.toLowerCase().contains("register"); print("registered : $registered"); if (registered) { prefs.setBool("registered", true); } } } catch (e) { print("Error while login $e"); } return loginResponse; } Future initUserData() async { await initCacheDatabase(); await refreshUserData(); print('Initializing UserData...'); if(Platform.isAndroid || Platform.isIOS){ Connectivity().onConnectivityChanged.listen((result) { offline = (result == ConnectivityResult.none); if (!offline) { UserOperations.executeQueries(); refreshUserData(); } }); } } Future refreshUserData() async { refreshStream.add(true); await updateCatsList(); await updateTasksList(); await updateActList(); refreshStream.add(false); NotificationManager.RescheduleNotifications(); } Future cacheDbExist() async { if(Platform.isAndroid || Platform.isIOS){ Directory directory = await getApplicationDocumentsDirectory(); return databaseFactory.databaseExists(directory.path + 'cache.db'); }else{ Directory directory = await getApplicationDocumentsDirectory(); return SqlFF.databaseFactoryFfi.databaseExists(directory.path + 'cache.db'); } } Future updateCatsList() async { //print('Updating with localCache'); // categories = await GetCategories(true); print('Checking if can refresh'); categories = await GetCategories(false); } Future updateTasksList() async { // print('Updating with localCache'); // taskTypes = await GetTaskTypes(true); print('Checking if can refresh'); taskTypes = await GetTaskTypes(false); } Future updateActList() async { //print('Updating with localCache'); //activities = await GetActivities(true); print('Checking if can refresh'); activities = await GetActivities(false); } Future initCacheDatabase() async { Directory directory = await getApplicationDocumentsDirectory(); print('database at ' + directory.path + 'cache.db'); if(Platform.isAndroid || Platform.isIOS) { cacheDb = await openDatabase(directory.path + 'cache.db', version: 1, onCreate: onCacheDatabaseCreate, onUpgrade: onCacheDatabaseUpgrade); }else{ cacheDb = await SqlFF.databaseFactoryFfi.openDatabase(directory.path + 'cache.db', options: OpenDatabaseOptions(version: 1,onCreate:onCacheDatabaseCreate )); } await UserOperations.executeQueries(); } void onCacheDatabaseCreate(Database db, int newVersion) async { String CategoriesTableSQL = 'CREATE TABLE Categories(${Category.colCatId} VARCHAR(255) PRIMARY KEY,${Category.colName} TEXT, ${Category.colColor} TEXT, ${Category.colProductive} INTEGER)'; // print(CategoriesTableSQL); await db.execute(CategoriesTableSQL); print("Initiated Categories Table"); String TaskTableSQL = 'CREATE TABLE TaskTypes(id TEXT PRIMARY KEY, ${TaskType.colName} TEXT, ${TaskType.colCategory} TEXT, ' 'FOREIGN KEY (${TaskType.colCategory}) REFERENCES Categories(${Category.colCatId}))'; // print(TaskTableSQL); await db.execute(TaskTableSQL); String ActivityTableSQL = 'CREATE TABLE Activities(id INTEGER PRIMARY KEY AUTOINCREMENT, ${Activity.colType} INT, ${Activity.colStartTime} DATETIME, ${Activity.colEndTime} DATETIME, ${Activity.colMetadata} TEXT, ' 'FOREIGN KEY (${Activity.colType}) REFERENCES TaskTypes(id))'; // print(ActivityTableSQL); await db.execute(ActivityTableSQL); String QueriesTableSQL = 'CREATE TABLE Queries(id INTEGER PRIMARY KEY AUTOINCREMENT, ${Queries.colLink} TEXT,${Queries.colData} TEXT)'; // print(QueriesTableSQL); await db.execute(QueriesTableSQL); final prefs = await SharedPreferences.getInstance(); if (prefs.getBool("registered") ?? false) { addInitialDataToCache(); prefs.setBool("registered", false); } // GetCategories(); } Future addInitialDataToCache() async { print("adding init data"); await Future.delayed(const Duration(seconds: 1)); //Insert Initial Entries for (Category element in InitialData.getCategories(username)) { await UserOperations.addCategory(element.name, element.color, element.productive, bulk: true); } for (TaskType element in InitialData.getTaskTypes(username)) { await UserOperations.addTaskType(element.name, element.category, bulk: true); // Map data = { // TaskType.colName: element.name, // TaskType.colCategory: element.category // }; // await cacheDb.insert('TaskTypes', data); } await UserOperations.executeQueries(); await refreshUserData(); } void onCacheDatabaseUpgrade(Database db, int oldVersion, int newVersion) async { //ValidateCacheDB(); print('Upgrading CacheDB from ver.$oldVersion to ver.$newVersion'); } Future> GetCategories(bool forceOffline) async { List _categories = []; if (offline || forceOffline) { //Retreive from cacheDB } else { //Check if server got updated, If not go for cache var android_id = await Settings.UUID(); //Validate device_id to check updates bool catsUpdated = false; // try { // http.Response update_response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/check_update.php'), body: {"username": username, "device_id": android_id})); // final data = update_response.body.split(','); // catsUpdated = data[0] == '1'; // } catch (e) { // print(e); // } print("Need to update : ${!catsUpdated}"); //Update CacheDB if(!catsUpdated){ await UpdateCategoriesFromServer(); } } List cats = await cacheDb.query('Categories'); print(cats.length); for (Map element in cats) { String? catName = element[Category.colName].toString(); String? catColor = element[Category.colColor].toString(); String? catProductive = element[Category.colProductive].toString(); if (catName == null || catColor == null || catProductive == null) { print("Something is null!"); print("name:{$catName}, color:{$catColor}, prod:{$Category.colProductive}"); continue; } // print("name:{$catName}, color:{$catColor}, prod:{$catProductive}"); _categories.add(Category(username + catName, catName, catColor, ParseBool(catProductive))); } categories = _categories; return categories; } Future UpdateCategoriesFromServer() async { print("Updating Categories as $username"); try { http.Response response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/get_categories.php'), body: {"username": username, "device_id": await Settings.UUID()})); print(response.body); List data = response.body.split(""); // await cacheDb.delete("Categories"); for (var value in data) { Map cat = jsonDecode(value); //print(catData); await cacheDb.rawInsert("INSERT OR REPLACE INTO Categories (${Category.colCatId},${Category.colName},${Category.colProductive},${Category.colColor}) " "VALUES ('${cat['category_id']}','${cat['name']}',${cat['productive']},'${cat['color']}') "); } } catch (e) { print("Error while cats $e"); } } Future> GetTaskTypes(bool forceOffline) async { List _taskTypes = []; if (offline || forceOffline) { //Retreive from cacheDB } else { //Check if server got updated, If not go for cache var android_id = await Settings.UUID(); bool updated = false; // try { // //Validate device_id to check updates // http.Response update_response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/check_update.php'), body: {"username": username, "device_id": android_id})); // final data = update_response.body.split(','); // updated = data[1] == '1'; // } catch (e) { // print(e); // } print("Need to update : ${!updated}"); //Update CacheDB if(!updated){ await UpdateTaskTypesFromServer(); } } await Future.delayed(Duration(seconds: 1)); List cats = await cacheDb.query('TaskTypes'); print(cats.length); for (Map element in cats) { String? id = element[TaskType.colId].toString(); String? name = element[TaskType.colName].toString(); String? category = element[TaskType.colCategory].toString(); Category? cat = await getCatFromId(category); if (id == null || name == null || category == null) { print("Something is null!"); print("name:{$name}, cat:{$category}, prod:{$id}"); continue; } // print("name:{$name}, cat:{$category}, prod:{$id}"); _taskTypes.add(TaskType(id, name, category, cat)); } taskTypes = _taskTypes; return taskTypes; } Future UpdateTaskTypesFromServer() async { // await GetCategories(true); print("Updating TaskTypes as $username"); try { http.Response response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/get_taskTypes.php'), body: {"username": username, "device_id": await Settings.UUID()})); print(response.body); List data = response.body.split(""); await cacheDb.delete("TaskTypes"); for (var value in data) { Map cat = jsonDecode(value); //print(cat); await cacheDb.rawInsert("INSERT OR REPLACE INTO TaskTypes (${TaskType.colId},${TaskType.colName},${TaskType.colCategory}) " "VALUES ('${cat['task_id']}','${cat['name']}','${cat['category_id']}') "); print(await cacheDb.query("TaskTypes")); } } catch (e) { print("Error while tasks $e"); } } Future> GetActivities(bool forceOffline) async { List _activities = []; if (offline || forceOffline) { //Retreive from cacheDB print('offline, refreshing activities'); } else { //Check if server got updated, If not go for cache var android_id = await Settings.UUID(); bool updated = false; // try { // //Validate device_id to check updates // http.Response update_response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/check_update.php'), body: {"username": username, "device_id": android_id})); // final data = update_response.body.split(','); // updated = data[2] == '1'; // } catch (e) { // print(e); // } print("Need to update activities : ${!updated}"); //Update CacheDB if(!updated){ await UpdateActivitiesFromServer(); } } List cats = await cacheDb.rawQuery('SELECT * FROM Activities ORDER BY ${Activity.colStartTime} DESC'); print(cats.length); for (Map element in cats) { int? id = element['id']; String? type = element[Activity.colType].toString(); String? startTime = element[Activity.colStartTime].toString(); String? endTime = element[Activity.colEndTime].toString(); String? metadata = element[Activity.colMetadata].toString(); TaskType? taskType = await getTaskFromId(type); if (type == null || startTime == null || endTime == null || taskType == null) { print("Something is null!\ntype:${type == null}, startTime:${startTime == null}, eTime:${endTime == null}, taskType${taskType == null}"); print("TaskType:{$type}, Start Time:{$startTime}, endTime:{$endTime}, metadata:${metadata}"); continue; } //print("TaskType:{$type}, Start Time:{$startTime}, endTime:{$endTime}, metadata:${metadata}"); DateTime sTime = DateTime.parse(startTime); DateTime eTime = DateTime.parse(endTime); if(eTime.day!=sTime.day){ DateTime midnight = DateTime(eTime.year,eTime.month,eTime.day,0,0,0); int firstHalf = eTime.difference(midnight).inMinutes; int secondHalf = midnight.difference(sTime).inMinutes; // print("${taskType.name} first half : $firstHalf -> second half: $secondHalf"); if(firstHalf>1){ _activities.add(Activity(taskType, midnight, eTime, metadata: metadata,tStartTime: sTime)); } if(secondHalf > 1) { _activities.add(Activity(taskType, sTime, midnight, metadata: metadata, tEndTime: eTime)); } }else{ _activities.add(Activity(taskType, DateTime.parse(startTime), DateTime.parse(endTime), metadata: metadata)); } } activities = _activities; return activities; } Future UpdateActivitiesFromServer() async { print("Updating Activities as $username"); try { http.Response response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/get_activities.php'), body: {"username": username, "device_id": await Settings.UUID()})); await cacheDb.rawDelete("DELETE FROM Activities"); print('Truncate Activity Table before'); print("Activity response: ${response.body}"); if (response.body.contains("{")) { List data = response.body.split(""); for (var value in data) { Map cat = jsonDecode(value); //print(cat); await cacheDb.rawInsert("INSERT OR REPLACE INTO Activities (${Activity.colType}, ${Activity.colStartTime}, ${Activity.colEndTime}, ${Activity.colMetadata}) " "VALUES ('${cat['task_id']}', '${cat['sTime']}','${cat['eTime']}', '${cat['metadata']}') "); } } else { print("No activities for now"); } } catch (e) { print("Error : $e @ updating activities"); print("Error while acts $e"); } } Future getTaskFromId(String taskId) async { // await GetTaskTypes(false); TaskType? cat = null; for (var element in taskTypes) { if (element.id == taskId) { cat = element; cat?.cat = await getCatFromId((cat?.category ?? '')); } } if (cat == null) { print('Got null tasktype for ${taskId} after searching on ${taskTypes.length}'); } return cat; } Future getCatFromId(String catId) async { // await GetTaskTypes(false); Category? cat = null; for (var element in categories) { if (element.category_id == catId) { cat = element; } } return cat; } //Helpers class Helpers { Future _getId() async { var deviceInfo = DeviceInfoPlugin(); if (Platform.isIOS) { // import 'dart:io' var iosDeviceInfo = await deviceInfo.iosInfo; return iosDeviceInfo.identifierForVendor; // unique ID on iOS } else { var androidDeviceInfo = await deviceInfo.androidInfo; return androidDeviceInfo.androidId; // unique ID on Android } } } bool ParseBool(obj) { return obj.toString().toLowerCase() == "true" || obj.toString() == "1"; } class UserOperations { static DateFormat dFormat = DateFormat("yyyy-MM-dd HH:mm:ss"); static Future addCategory(String name, String color, bool productive, {bool bulk = false}) async { Map queryBody = {'username': username, 'device_id': await Settings.UUID(), 'name': name, 'color': color, 'productive': productive ? '1' : '0'}; //Add Query Map query = {Queries.colLink: 'add_category', Queries.colData: jsonEncode(queryBody)}; print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); await cacheDb.insert('Queries', query); //update Cache Map data = {Category.colCatId: username + name, Category.colName: name, Category.colColor: color, Category.colProductive: productive}; await cacheDb.insert('Categories', data); await refreshUserData(); if (!bulk) { //Add to server and refresh Cache await executeQueries(); } } static Future addTaskType(String name, String category, {bool bulk = false}) async { Map queryBody = {'id': username + name, 'username': username, 'device_id': await Settings.UUID(), 'name': name, 'category': username + category}; //Add Query Map query = {Queries.colLink: 'add_taskType', Queries.colData: jsonEncode(queryBody)}; print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); await cacheDb.insert('Queries', query); //update Cache Map data = {TaskType.colId: username + name, Category.colName: name, Category.colCatId: username + category}; await cacheDb.insert('TaskTypes', data); await refreshUserData(); if (!bulk) { //Add to server and refresh Cache await executeQueries(); } } static Future addActivity(String type, DateTime sTime, DateTime eTime, {String metadata = 'null', bool bulk = false, Function(int)? onOverlap}) async { //Check for timeoverlapse activities = await GetActivities(true); int? overlapCount = Sqflite.firstIntValue(await cacheDb.rawQuery( "SELECT COUNT(*) FROM Activities WHERE (((${Activity.colStartTime} < datetime('$sTime')) AND ((${Activity.colEndTime} > datetime('$eTime')) OR (${Activity.colEndTime} < datetime('$eTime') AND ${Activity.colEndTime} > datetime('$sTime')))) OR (${Activity.colStartTime} > datetime('$sTime') AND ${Activity.colStartTime} < datetime('$eTime')) OR (${Activity.colStartTime}=datetime('$sTime') AND ${Activity.colEndTime}=datetime('$eTime')))")); print("ActivityOverlaps: $overlapCount"); if (overlapCount! > 0) { onOverlap!(overlapCount); return; } Map queryBody = { 'username': username, 'device_id': await Settings.UUID(), 'type': username + type, 'sTime': dFormat.format(sTime), 'eTime': dFormat.format(eTime), 'metadata': metadata }; if (metadata.length > 0) {} //Add Query Map query = {Queries.colLink: 'add_activity', Queries.colData: jsonEncode(queryBody)}; print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); await cacheDb.insert('Queries', query); //update Cache Map data = {Activity.colType: username + type, Activity.colStartTime: dFormat.format(sTime), Activity.colEndTime: dFormat.format(eTime), Activity.colMetadata: metadata}; await cacheDb.insert('Activities', data); await refreshUserData(); if (!bulk) { //Add to server and refresh Cache await executeQueries(); } } static Future editActivity(DateTime init_sTime, DateTime init_eTime,String type, DateTime sTime, DateTime eTime, {String metadata = 'null', bool bulk = false, Function(int)? onOverlap}) async { //Check for timeoverlapse activities = await GetActivities(true); int? overlapCount = Sqflite.firstIntValue(await cacheDb.rawQuery( "SELECT COUNT(*) FROM Activities WHERE (((${Activity.colStartTime} < datetime('$sTime')) AND ((${Activity.colEndTime} > datetime('$eTime')) OR (${Activity.colEndTime} < datetime('$eTime') AND ${Activity.colEndTime} > datetime('$sTime')))) OR (${Activity.colStartTime} > datetime('$sTime') AND ${Activity.colStartTime} < datetime('$eTime')) OR (${Activity.colStartTime}=datetime('$sTime') AND ${Activity.colEndTime}=datetime('$eTime'))) AND ${Activity.colStartTime}!=datetime('${init_sTime}') AND ${Activity.colEndTime} != datetime('${init_eTime}')")); print("ActivityOverlaps: $overlapCount"); if (overlapCount! > 0) { onOverlap!(overlapCount); return; } Map queryBody = { 'username': username, 'device_id': await Settings.UUID(), 'type': username + type, 'init_sTime': dFormat.format(init_sTime), 'init_eTime':dFormat.format(init_eTime), 'sTime': dFormat.format(sTime), 'eTime': dFormat.format(eTime), 'metadata': metadata }; if (metadata.length > 0) {} //Add Query Map query = {Queries.colLink: 'edit_activity', Queries.colData: jsonEncode(queryBody)}; print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); await cacheDb.insert('Queries', query); //update Cache // Map data = {Activity.colType: username + type, Activity.colStartTime: dFormat.format(sTime), Activity.colEndTime: dFormat.format(eTime), Activity.colMetadata: metadata}; // String updateActQuery = "UPDATE Activities SET ${Activity.colType}="; // await cacheDb.insert('Activities', data); await refreshUserData(); if (!bulk) { //Add to server and refresh Cache await executeQueries(); } } static Future deleteTask(String name, {bulk = false}) async { Map queryBody = { 'id': username + name, 'username': username, 'device_id': await Settings.UUID(), }; //Add Query Map query = {Queries.colLink: 'delete_taskType', Queries.colData: jsonEncode(queryBody)}; print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); await cacheDb.insert('Queries', query); //update Cache Map data = { TaskType.colId: username + name, Category.colName: name, }; await cacheDb.rawDelete("DELETE FROM TaskTypes WHERE id='${username + name}'"); await refreshUserData(); //Add to server and refresh Cache if (!bulk) { await executeQueries(); } } static Future deleteCategory(String name, {bulk = false}) async { Map queryBody = { 'id': username + name, 'username': username, 'device_id': await Settings.UUID(), }; //Add Query Map query = {Queries.colLink: 'delete_category', Queries.colData: jsonEncode(queryBody)}; print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); await cacheDb.insert('Queries', query); //update Cache Map data = { TaskType.colId: username + name, Category.colName: name, }; await cacheDb.rawDelete("DELETE FROM Categories WHERE ${Category.colCatId}='${username + name}'"); await refreshUserData(); //Add to server and refresh Cache if (!bulk) { await executeQueries(); } } static Future deleteActivity(Activity activity, {bulk = false}) async { Map queryBody = { 'username': username, 'device_id': await Settings.UUID(), 'sTime': activity.startTime.toString(), 'eTime': activity.endTime.toString(), }; //Add Query Map query = {Queries.colLink: 'delete_activity', Queries.colData: jsonEncode(queryBody)}; print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); await cacheDb.insert('Queries', query); //update Cache String deleteQuery = "DELETE FROM Activities WHERE ${Activity.colStartTime}=datetime('${dFormat.format(activity.startTime)}') AND ${Activity.colEndTime}=datetime('${dFormat.format(activity.endTime)}')"; print("delteQuery : $deleteQuery"); await cacheDb.rawDelete(deleteQuery); await refreshUserData(); //Add to server and refresh Cache if (!bulk) { await executeQueries(); } } static Future executeQueries() async { if (offline) { print("Cannot executre queries, Offline!"); return; } List> queries = await cacheDb.query('Queries'); for (Map element in queries) { int id = int.parse(element['id'].toString()); String? file = element[Queries.colLink].toString(); String? data = element[Queries.colData].toString(); if (file == null || data == null) { print("Null query, Ignoring..."); continue; } print("Query[\n file:$file, \ndata:$data]"); //Execute the http here Map body = jsonDecode(data); try { http.Response queryResponse = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/$file.php'), body: body)); print("Query executed : Results{${queryResponse.body}"); if (queryResponse.body.toLowerCase().contains("success")) { await cacheDb.rawDelete('DELETE FROM Queries WHERE id=$id'); } offline = false; } catch (e) { print("Error while query $e"); } } await refreshUserData(); } }