This commit is contained in:
warlock
2022-03-05 18:48:18 +05:30
parent 7f50983e11
commit 0272b2d1fd
53 changed files with 2233 additions and 440 deletions

View File

@@ -1,10 +1,19 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.Xperience.TaskTracker.tasktracker"> package="com.Xperience.TaskTracker.tasktracker">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application <application
android:label="tasktracker" android:label="tasktracker"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
</intent-filter>
</receiver>
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

BIN
images/empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1,12 +1,10 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'main.dart'; import 'main.dart' as Main;
import 'newActivity.dart'; import 'newActivity.dart';
import 'Data.dart'; import 'Data.dart';
import 'User.dart' as User; import 'User.dart' as User;
import 'package:sn_progress_dialog/sn_progress_dialog.dart';
class Activities extends StatefulWidget { class Activities extends StatefulWidget {
const Activities({Key? key}) : super(key: key); const Activities({Key? key}) : super(key: key);
@@ -14,9 +12,10 @@ class Activities extends StatefulWidget {
_ActivitiesState createState() => _ActivitiesState(); _ActivitiesState createState() => _ActivitiesState();
} }
late ProgressDialog progressDialog;
class _ActivitiesState extends State<Activities> { class _ActivitiesState extends State<Activities> {
//late ProgressDialog progressDialog;
TextEditingController searchController = TextEditingController(); TextEditingController searchController = TextEditingController();
FocusNode _focus = FocusNode(); FocusNode _focus = FocusNode();
bool searching = false; bool searching = false;
@@ -38,6 +37,7 @@ class _ActivitiesState extends State<Activities> {
super.initState(); super.initState();
_focus.addListener(_onFocusChange); _focus.addListener(_onFocusChange);
UpdateList(); UpdateList();
//init(context);
} }
@override @override
@@ -47,10 +47,20 @@ class _ActivitiesState extends State<Activities> {
_focus.removeListener(_onFocusChange); _focus.removeListener(_onFocusChange);
_focus.dispose(); _focus.dispose();
} }
void UpdateList() async {
try {
//progressDialog.show(max: 100, msg: 'Loading Activities');
} catch (e) {}
await User.refreshUserData();
setState(() {});
try {
// progressDialog.update(value: 100);
} catch (e) {}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
progressDialog = ProgressDialog(context: context); // progressDialog = ProgressDialog(context: context);
return Scaffold( return Scaffold(
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () { onPressed: () {
@@ -129,9 +139,9 @@ class _ActivitiesState extends State<Activities> {
), ),
InkWell( InkWell(
onTap: () { onTap: () {
setState(() async { UpdateList();
await User.refreshUserData(); setState(() {
UpdateList();
}); });
}, },
child: Icon(Icons.refresh, size: 30), child: Icon(Icons.refresh, size: 30),
@@ -140,7 +150,7 @@ class _ActivitiesState extends State<Activities> {
), ),
], ],
)), )),
drawer: navDrawer(context, 2), drawer: Main.navDrawer(context, 2),
body: Container( body: Container(
padding: EdgeInsets.all(0), padding: EdgeInsets.all(0),
child: SingleChildScrollView( child: SingleChildScrollView(
@@ -149,16 +159,6 @@ class _ActivitiesState extends State<Activities> {
)))); ))));
} }
void UpdateList() async {
try {
progressDialog.show(max: 100, msg: 'Loading Activities');
} catch (e) {}
await User.updateActList();
setState(() {});
try {
progressDialog.update(value: 100);
} catch (e) {}
}
List<Widget> PrintTasks() { List<Widget> PrintTasks() {
List<Widget> _tasks = []; List<Widget> _tasks = [];
@@ -214,9 +214,10 @@ class _ActivitiesState extends State<Activities> {
if (element.taskType.cat == null) { if (element.taskType.cat == null) {
print('Got some null cat : ${element.taskType.name}'); print('Got some null cat : ${element.taskType.name}');
} else { } else {
Color color = HexColor.fromHex(element.taskType.cat?.color ?? '#000000'); Color color = Main.HexColor.fromHex(element.taskType.cat?.color ?? '#000000');
bool productive = element.taskType.cat?.productive ?? true; bool productive = element.taskType.cat?.productive ?? true;
Widget task = ActivityCard(context, name, element.startTime, element.endTime, productive, color, element, totalMinutes[thisDate] ?? 0); Widget task = ActivityCard(context, name, element.startTime, element.endTime, productive, color, element, totalMinutes[thisDate] ?? 0);
// print('Activity : ${name} ,sTime: ${element.startTime}, eTime: ${element.endTime}');
_tasks.add(task); _tasks.add(task);
} }
} }
@@ -252,7 +253,7 @@ class _ActivitiesState extends State<Activities> {
children: [ children: [
Container( Container(
child: Align( child: Align(
child: FittedBox(fit: BoxFit.fitWidth,child: Text(MinutesToTimeString(prodActs),)), child: FittedBox(fit: BoxFit.fitWidth,child: Text(Main.MinutesToTimeString(prodActs),)),
alignment: Alignment.center, alignment: Alignment.center,
), ),
width: (prodPercentage) * 1.7, width: (prodPercentage) * 1.7,
@@ -261,7 +262,7 @@ class _ActivitiesState extends State<Activities> {
), ),
Container( Container(
child: Align( child: Align(
child: Text(MinutesToTimeString(unprodActs)), child: Text(Main.MinutesToTimeString(unprodActs)),
alignment: Alignment.center, alignment: Alignment.center,
), ),
width: (100 - prodPercentage) * 1.7, width: (100 - prodPercentage) * 1.7,
@@ -400,7 +401,7 @@ class _ActivitiesState extends State<Activities> {
} }
void DeleteSelectedTasks() async { void DeleteSelectedTasks() async {
progressDialog.show(max: 100, msg: 'Deleteing ${selectedActivities.length} Activities'); //progressDialog.show(max: 100, msg: 'Deleteing ${selectedActivities.length} Activities');
selectedActivities.forEach((element) async { selectedActivities.forEach((element) async {
await User.UserOperations.deleteActivity(element, bulk: true); await User.UserOperations.deleteActivity(element, bulk: true);
}); });
@@ -411,7 +412,7 @@ class _ActivitiesState extends State<Activities> {
selectedActivities = []; selectedActivities = [];
selecting = false; selecting = false;
setState(() { setState(() {
progressDialog.update(value: 100); // progressDialog.update(value: 100);
}); });
} }
} }

View File

@@ -4,7 +4,6 @@ import 'main.dart';
import 'NewTask.dart'; import 'NewTask.dart';
import 'User.dart' as User; import 'User.dart' as User;
import 'Data.dart'; import 'Data.dart';
import 'package:sn_progress_dialog/sn_progress_dialog.dart';
class Categories extends StatefulWidget { class Categories extends StatefulWidget {
const Categories({Key? key}) : super(key: key); const Categories({Key? key}) : super(key: key);
@@ -12,20 +11,25 @@ class Categories extends StatefulWidget {
_CategoriesState createState() => _CategoriesState(); _CategoriesState createState() => _CategoriesState();
} }
late ProgressDialog progressDialog;
bool selecting=false; bool selecting=false;
class _CategoriesState extends State<Categories> { class _CategoriesState extends State<Categories> {
@override @override
void initState() { void initState() {
// TODO: implement initState // TODO: implement initState
super.initState(); super.initState();
//init(context);
UpdateList(); UpdateList();
} }
@override void dispose() {
// TODO: implement dispose
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
progressDialog=ProgressDialog(context: context);
return Scaffold( return Scaffold(
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () { onPressed: () {
@@ -66,7 +70,7 @@ class _CategoriesState extends State<Categories> {
} }
void UpdateList() async { void UpdateList() async {
await User.updateCatsList(); await User.refreshUserData();
setState(() {}); setState(() {});
} }
@@ -92,7 +96,6 @@ class _CategoriesState extends State<Categories> {
} }
void DeleteSelectedCats() async{ void DeleteSelectedCats() async{
progressDialog.show(max: 100, msg: 'Deleteing ${selectedTasks.length} Categories');
selectedTasks.forEach((element) async { selectedTasks.forEach((element) async {
await User.UserOperations.deleteCategory(element, bulk:true); await User.UserOperations.deleteCategory(element, bulk:true);
}); });
@@ -102,7 +105,6 @@ class _CategoriesState extends State<Categories> {
selectedTasks=[]; selectedTasks=[];
selecting=false; selecting=false;
setState(() { setState(() {
progressDialog.update(value: 100);
}); });
} }

View File

@@ -1,9 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'theme_provider.dart'; import 'theme_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'User.dart' as User;
class Category{ class Category{
Category(this.category_id, this.name, this.color, this.productive); Category(this.category_id, this.name, this.color, this.productive);
@@ -99,10 +101,47 @@ class Settings{
} }
} }
static Future<void> setTheme(int value) async{ static Future<void> setTheme(int value) async{
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.setInt("theme", value); await prefs.setInt("theme", value);
} }
static String notification_key= "notification_interval";
static Future<int> getNotificationInterval() async{
final prefs = await SharedPreferences.getInstance();
int _value = 1;
if(prefs.containsKey(notification_key)){
_value = await prefs.getInt(notification_key) ?? 1;
}else{
prefs.setInt(notification_key,_value);
}
return _value;
}
static Future<void> setNotificationInterval(int value) async{
final prefs = await SharedPreferences.getInstance();
prefs.setInt(notification_key, value);
}
static List<String> notificationOptions = <String>['Off','1 hour', '2 hour', '3 hour', '4 hour', '5 hour', '6 hour'];
static bool adaptiveNotificationAvailable() {
List<String> dates = [];
if(User.activities.length < 10){
return false;
}else{
for (var element in User.activities) {
String thisDate = DateFormat("MM/dd").format(element.startTime);
if(!dates.contains(thisDate)){
dates.add(thisDate);
}
}
}
return (dates.length > 2);
}
} }
final settings = Settings(); final settings = Settings();

41
lib/Dialogs.dart Normal file
View File

@@ -0,0 +1,41 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'main.dart';
class Dialogs{
static bool showing = false;
static BuildContext? context;
static List<Widget> popupsOpened=[];
static waiting( String title){
showing=true;
context=navigatorKey.currentContext;
if(context!=null) {
return showDialog(
context: context!,
barrierDismissible: false,
routeSettings: const RouteSettings(name: "Progress"),
builder: (BuildContext context) {
return AlertDialog(
title: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SpinKitChasingDots(color: Colors.green),
Expanded(child: Text(title,textAlign: TextAlign.center,)),
],
),
);
}
);
}
}
static hide(){
showing=false;
Navigator.of(navigatorKey.currentContext!).popUntil((route){
return route.settings.name!="Progress";
});
}
}

View File

@@ -161,7 +161,9 @@ class _NewCategoryState extends State<NewCategory> {
} }
var hex = '#${pickerColor.value.toRadixString(16)}'; var hex = '#${pickerColor.value.toRadixString(16)}';
await User.UserOperations.addCategory(catName, hex, productive); await User.UserOperations.addCategory(catName, hex, productive);
Navigator.of(context).pop(); Navigator.of(context).popUntil((route){
return route.isFirst;
});
} }
} }

View File

@@ -148,7 +148,9 @@ class _NewTaskState extends State<NewTask> {
return; return;
} }
await User.UserOperations.addTaskType(catName,selectedCat); await User.UserOperations.addTaskType(catName,selectedCat);
Navigator.of(context).pop(); Navigator.of(context).popUntil((route){
return route.isFirst;
});
} }
} }

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tasktracker/Data.dart';
class NotificationSettings extends StatefulWidget { class NotificationSettings extends StatefulWidget {
const NotificationSettings({Key? key}) : super(key: key); const NotificationSettings({Key? key}) : super(key: key);
@@ -8,6 +9,26 @@ class NotificationSettings extends StatefulWidget {
} }
class _NotificationSettingsState extends State<NotificationSettings> { class _NotificationSettingsState extends State<NotificationSettings> {
String dropdownValue = Settings.notificationOptions[1];
bool suggestionNotification = true;
bool adaptiveNotification =true;
bool adaptiveNotificationAvailable =false;
@override void initState() {
// TODO: implement initState
super.initState();
updateSettings();
}
void updateSettings() async{
int notificationInterval= await Settings.getNotificationInterval();
adaptiveNotificationAvailable = Settings.adaptiveNotificationAvailable();
setState(() {
dropdownValue=Settings.notificationOptions[notificationInterval];
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -19,13 +40,51 @@ class _NotificationSettingsState extends State<NotificationSettings> {
height: 10, height: 10,
), ),
ListTile( ListTile(
enabled: adaptiveNotificationAvailable,
title: Text("Adaptive Notifications"),
trailing:(adaptiveNotificationAvailable) ?Switch.adaptive(value: adaptiveNotification, onChanged: (val){
adaptiveNotification=val;
setState(() {
});
}) : Text("Track more data to activate this", style: TextStyle(color: Colors.red)),
subtitle: Text("Notifies you to track activities according to your past activities patterns"),
),
ListTile(
enabled: !adaptiveNotification,
title: Text("New Activity Notification"), title: Text("New Activity Notification"),
trailing: InkWell(onTap:(){},child: Text("1 hour")), trailing:(adaptiveNotification) ? (Text("Adaptive")) : DropdownButton<String>(
value: dropdownValue,
icon: const Icon(Icons.arrow_downward),
elevation: 16,
underline: Container(
height: 2,
color: Colors.red,
),
onChanged: (String? newValue) {
setState(() {
Settings.setNotificationInterval(Settings.notificationOptions.indexOf(newValue!));
dropdownValue = newValue!;
});
},
items:Settings.notificationOptions.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
subtitle: Text("Notify you to track activities of past time"), subtitle: Text("Notify you to track activities of past time"),
), ),
Divider(), Divider(),
ListTile( ListTile(
title: Text("Suggestion Notifications"), trailing:Switch.adaptive(value: true, onChanged: (val){}), title: Text("Suggestion Notifications"), trailing:Switch.adaptive(value: suggestionNotification, onChanged: (val){
suggestionNotification=val;
setState(() {
});
}),
subtitle: Text("Notifies you about suggestions according to your data"), subtitle: Text("Notifies you about suggestions according to your data"),
) )

View File

@@ -3,9 +3,7 @@ import 'main.dart';
import 'NewTask.dart'; import 'NewTask.dart';
import 'Data.dart'; import 'Data.dart';
import 'User.dart' as User; import 'User.dart' as User;
import 'package:sn_progress_dialog/sn_progress_dialog.dart';
late ProgressDialog progressDialog;
class Tasks extends StatefulWidget { class Tasks extends StatefulWidget {
const Tasks({Key? key}) : super(key: key); const Tasks({Key? key}) : super(key: key);
@@ -17,10 +15,29 @@ class _TasksState extends State<Tasks> {
void initState() { void initState() {
// TODO: implement initState // TODO: implement initState
super.initState(); super.initState();
progressDialog = ProgressDialog(context: context);
User.progressDialog=progressDialog;
UpdateList(); UpdateList();
// init(context);
}
var refreshSub;
void init(BuildContext context) async{
await Future.delayed(Duration(seconds: 1));
refreshSub = User.refreshStream.stream.listen((value) {
print("Streaming refresh : $value");
if(value){
// dialogs.waiting(context, "Syncing");
print("Opening progress dialog");
}else{
// dialogs.hide(context);
print("Closing progress dialog");
}
});
}
@override void dispose() {
// TODO: implement dispose
super.dispose();
refreshSub?.cancel();
} }
@override @override
@@ -67,12 +84,12 @@ class _TasksState extends State<Tasks> {
void UpdateList() async { void UpdateList() async {
try{progressDialog.show(max:100, msg: 'Loading Task Types...');}catch(e){} // try{progressDialog.show(max:100, msg: 'Loading Task Types...');}catch(e){}
await User.updateTasksList(); await User.refreshUserData();
// hideProgressDialog(); // hideProgressDialog();
setState(() {}); if(mounted)setState(() {});
try{progressDialog.update(value: 100);}catch(e){} // try{progressDialog.update(value: 100);}catch(e){}
} }
List<Widget> PrintTasks() { List<Widget> PrintTasks() {
@@ -171,7 +188,6 @@ class _TasksState extends State<Tasks> {
} }
void DeleteSelectedTasks() async{ void DeleteSelectedTasks() async{
progressDialog.show(max: 100, msg: "Deleting ${selectedTasks.length} ");
selectedTasks.forEach((element) async { selectedTasks.forEach((element) async {
await User.UserOperations.deleteTask(element, bulk:true); await User.UserOperations.deleteTask(element, bulk:true);
}); });
@@ -181,7 +197,6 @@ class _TasksState extends State<Tasks> {
selectedTasks=[]; selectedTasks=[];
selecting=false; selecting=false;
setState(() { setState(() {
progressDialog.update(value: 100);
}); });
} }

View File

@@ -1,7 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'main.dart'; import 'main.dart';
@@ -11,45 +13,38 @@ import 'Data.dart';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:sn_progress_dialog/sn_progress_dialog.dart';
import 'Tasks.dart'; import 'Tasks.dart';
late ProgressDialog? progressDialog;
late http.Response loginResponse; late http.Response loginResponse;
late Database cacheDb; late Database cacheDb;
late String username; late String username;
List<Category> categories = []; List<Category> categories = [];
List<TaskType> taskTypes = []; List<TaskType> taskTypes = [];
List<Activity> activities=[]; List<Activity> activities = [];
bool offline = true; bool offline = true;
bool registered = false; bool registered = false;
bool refreshing = true; StreamController<bool> refreshStream = StreamController<bool>();
Future<http.Response> login(String _username, String password) async { Future<http.Response> login(String _username, String password) async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
username = _username; username = _username;
var device_id = await Settings.UUID(); var device_id = await Settings.UUID();
try { try {
loginResponse = (await http.post( loginResponse = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/login.php'), body: <String, String>{"username": _username, "password": password, "device_id": device_id}));
Uri.parse('http://161.97.127.136/task_tracker/login.php'),
body: <String, String>{
"username": _username,
"password": password,
"device_id": device_id
}));
if (loginResponse.body.toLowerCase().contains("success")) { if (loginResponse.body.toLowerCase().contains("success")) {
offline = false; offline = false;
username = _username; username = _username;
registered =loginResponse.body.toLowerCase().contains("register"); registered = loginResponse.body.toLowerCase().contains("register");
print("registered : $registered"); print("registered : $registered");
if(registered){ if (registered) {
prefs.setBool("registered", true); prefs.setBool("registered", true);
} }
} }
}catch(e){ } catch (e) {
offline=true; print("Error while login $e");
} }
return loginResponse; return loginResponse;
} }
@@ -58,52 +53,48 @@ Future<void> initUserData() async {
await initCacheDatabase(); await initCacheDatabase();
await refreshUserData(); await refreshUserData();
print('Initializing UserData...'); print('Initializing UserData...');
if (offline) { Connectivity().onConnectivityChanged.listen((result) {
print('Going offline mode.'); offline = (result == ConnectivityResult.none);
} if (!offline) {
UserOperations.executeQueries();
refreshUserData();
}
});
} }
bool userDataInitiated =false;
Future<void> refreshUserData() async{ Future<void> refreshUserData() async {
ShowProgress("Loading data"); refreshStream.add(true);
refreshing = true;
// categories= await GetCategories(true);
// taskTypes= await GetTaskTypes(true);
// activities= await GetActivities(true);
await updateCatsList(); await updateCatsList();
await updateTasksList(); await updateTasksList();
await updateActList(); await updateActList();
userDataInitiated=true; refreshStream.add(false);
refreshing = false;
HideProgress();
} }
Future<bool> cacheDbExist() async {
Future<bool> cacheDbExist() async{
Directory directory = await getApplicationDocumentsDirectory(); Directory directory = await getApplicationDocumentsDirectory();
return databaseFactory.databaseExists(directory.path + 'cache.db'); return databaseFactory.databaseExists(directory.path + 'cache.db');
} }
Future<void> updateCatsList() async{ Future<void> updateCatsList() async {
//print('Updating with localCache'); //print('Updating with localCache');
// categories = await GetCategories(true); // categories = await GetCategories(true);
print('Checking if can refresh'); print('Checking if can refresh');
categories = await GetCategories(false); categories = await GetCategories(false);
} }
Future<void> updateTasksList() async{ Future<void> updateTasksList() async {
// print('Updating with localCache'); // print('Updating with localCache');
// taskTypes = await GetTaskTypes(true); // taskTypes = await GetTaskTypes(true);
print('Checking if can refresh'); print('Checking if can refresh');
taskTypes = await GetTaskTypes(false); taskTypes = await GetTaskTypes(false);
} }
Future<void> updateActList() async{ Future<void> updateActList() async {
//print('Updating with localCache'); //print('Updating with localCache');
//activities = await GetActivities(true); //activities = await GetActivities(true);
print('Checking if can refresh'); print('Checking if can refresh');
activities = await GetActivities(false); activities = await GetActivities(false);
} }
Future<void> initCacheDatabase() async { Future<void> initCacheDatabase() async {
Directory directory = await getApplicationDocumentsDirectory(); Directory directory = await getApplicationDocumentsDirectory();
print('database at ' + directory.path + 'cache.db'); print('database at ' + directory.path + 'cache.db');
@@ -113,22 +104,20 @@ Future<void> initCacheDatabase() async {
} }
void onCacheDatabaseCreate(Database db, int newVersion) async { void onCacheDatabaseCreate(Database db, int newVersion) async {
String CategoriesTableSQL = String CategoriesTableSQL = 'CREATE TABLE Categories(${Category.colCatId} VARCHAR(255) PRIMARY KEY,${Category.colName} TEXT, ${Category.colColor} TEXT, ${Category.colProductive} INTEGER)';
'CREATE TABLE Categories(${Category.colCatId} VARCHAR(255) PRIMARY KEY,${Category.colName} TEXT, ${Category.colColor} TEXT, ${Category.colProductive} INTEGER)';
// print(CategoriesTableSQL); // print(CategoriesTableSQL);
await db.execute(CategoriesTableSQL); await db.execute(CategoriesTableSQL);
print("Initiated Categories Table"); print("Initiated Categories Table");
String TaskTableSQL = String TaskTableSQL = 'CREATE TABLE TaskTypes(id TEXT PRIMARY KEY, ${TaskType.colName} TEXT, ${TaskType.colCategory} TEXT, '
'CREATE TABLE TaskTypes(id TEXT PRIMARY KEY, ${TaskType.colName} TEXT, ${TaskType.colCategory} TEXT, '
'FOREIGN KEY (${TaskType.colCategory}) REFERENCES Categories(${Category.colCatId}))'; 'FOREIGN KEY (${TaskType.colCategory}) REFERENCES Categories(${Category.colCatId}))';
// print(TaskTableSQL); // print(TaskTableSQL);
await db.execute(TaskTableSQL); await db.execute(TaskTableSQL);
String ActivityTableSQL = String ActivityTableSQL =
'CREATE TABLE Activities(id INTEGER PRIMARY KEY AUTOINCREMENT, ${Activity.colType} INT, ${Activity.colStartTime} DATETIME, ${Activity.colEndTime} DATETIME, ${Activity.colMetadata} TEXT, ' '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))'; 'FOREIGN KEY (${Activity.colType}) REFERENCES TaskTypes(id))';
// print(ActivityTableSQL); // print(ActivityTableSQL);
await db.execute(ActivityTableSQL); await db.execute(ActivityTableSQL);
String QueriesTableSQL = 'CREATE TABLE Queries(id INTEGER PRIMARY KEY AUTOINCREMENT, ${Queries.colLink} TEXT,${Queries.colData} TEXT)'; String QueriesTableSQL = 'CREATE TABLE Queries(id INTEGER PRIMARY KEY AUTOINCREMENT, ${Queries.colLink} TEXT,${Queries.colData} TEXT)';
@@ -136,24 +125,23 @@ void onCacheDatabaseCreate(Database db, int newVersion) async {
await db.execute(QueriesTableSQL); await db.execute(QueriesTableSQL);
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
if(prefs.getBool("registered") ?? false) { if (prefs.getBool("registered") ?? false) {
addInitialDataToCache(); addInitialDataToCache();
prefs.setBool("registered", false); prefs.setBool("registered", false);
} }
// GetCategories(); // GetCategories();
} }
Future<void> addInitialDataToCache() async{ Future<void> addInitialDataToCache() async {
ShowProgress("Initializing User Data");
print("adding init data"); print("adding init data");
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
//Insert Initial Entries //Insert Initial Entries
for(Category element in InitialData.getCategories(username)){ for (Category element in InitialData.getCategories(username)) {
await UserOperations.addCategory(element.name, element.color, element.productive,bulk: true); await UserOperations.addCategory(element.name, element.color, element.productive, bulk: true);
} }
for(TaskType element in InitialData.getTaskTypes(username)){ for (TaskType element in InitialData.getTaskTypes(username)) {
await UserOperations.addTaskType(element.name, element.category, bulk: true); await UserOperations.addTaskType(element.name, element.category, bulk: true);
// Map<String,Object> data = { // Map<String,Object> data = {
// TaskType.colName: element.name, // TaskType.colName: element.name,
@@ -161,7 +149,6 @@ Future<void> addInitialDataToCache() async{
// }; // };
// await cacheDb.insert('TaskTypes', data); // await cacheDb.insert('TaskTypes', data);
} }
HideProgress();
await UserOperations.executeQueries(); await UserOperations.executeQueries();
await refreshUserData(); await refreshUserData();
} }
@@ -171,281 +158,250 @@ void onCacheDatabaseUpgrade(Database db, int oldVersion, int newVersion) async {
print('Upgrading CacheDB from ver.$oldVersion to ver.$newVersion'); print('Upgrading CacheDB from ver.$oldVersion to ver.$newVersion');
} }
Future<List<Category>> GetCategories(bool forceOffline) async {
Future<List<Category>> GetCategories(bool forceOffline) async{
List<Category> _categories = []; List<Category> _categories = [];
if(offline || forceOffline){ if (offline || forceOffline) {
//Retreive from cacheDB //Retreive from cacheDB
}else{ } else {
//Check if server got updated, If not go for cache //Check if server got updated, If not go for cache
var android_id = await Settings.UUID(); var android_id = await Settings.UUID();
//Validate device_id to check updates //Validate device_id to check updates
bool catsUpdated = true; bool catsUpdated = true;
try{ try {
http.Response update_response = (await http.post( http.Response update_response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/check_update.php'), body: <String, String>{"username": username, "device_id": android_id}));
Uri.parse('http://161.97.127.136/task_tracker/check_update.php'),
body: <String, String>{"username": username, "device_id":android_id}));
final data = update_response.body.split(','); final data = update_response.body.split(',');
catsUpdated = data[0] == '1'; catsUpdated = data[0] == '1';
}catch(e){ } catch (e) {
print(e); print(e);
} }
print("Need to update : ${!catsUpdated}"); print("Need to update : ${!catsUpdated}");
//Update CacheDB //Update CacheDB
//if(!catsUpdated){ if(!catsUpdated){
await UpdateCategoriesFromServer(); await UpdateCategoriesFromServer();
// } }
} }
List<Map> cats = await cacheDb.query('Categories'); List<Map> cats = await cacheDb.query('Categories');
print(cats.length); print(cats.length);
for(Map element in cats){ for (Map element in cats) {
String? catName = element[Category.colName].toString(); String? catName = element[Category.colName].toString();
String? catColor = element[Category.colColor].toString(); String? catColor = element[Category.colColor].toString();
String? catProductive = element[Category.colProductive].toString(); String? catProductive = element[Category.colProductive].toString();
if(catName==null || catColor==null || catProductive==null){ if (catName == null || catColor == null || catProductive == null) {
print("Something is null!"); print("Something is null!");
print("name:{$catName}, color:{$catColor}, prod:{$Category.colProductive}"); print("name:{$catName}, color:{$catColor}, prod:{$Category.colProductive}");
continue; continue;
} }
print("name:{$catName}, color:{$catColor}, prod:{$catProductive}"); // print("name:{$catName}, color:{$catColor}, prod:{$catProductive}");
_categories.add(Category(username + catName, catName, catColor, ParseBool(catProductive))); _categories.add(Category(username + catName, catName, catColor, ParseBool(catProductive)));
} }
categories = _categories; categories = _categories;
return categories; return categories;
} }
Future<void> UpdateCategoriesFromServer() async{ Future<void> UpdateCategoriesFromServer() async {
print("Updating Categories as $username"); print("Updating Categories as $username");
try { try {
http.Response response = (await http.post( http.Response response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/get_categories.php'), body: <String, String>{"username": username, "device_id": await Settings.UUID()}));
Uri.parse('http://161.97.127.136/task_tracker/get_categories.php'),
body: <String, String>{
"username": username,
"device_id": await Settings.UUID()
}));
print(response.body); print(response.body);
List<String> data = response.body.split("<td>"); List<String> data = response.body.split("<td>");
// await cacheDb.delete("Categories"); // await cacheDb.delete("Categories");
for (var value in data) { for (var value in data) {
Map<String, dynamic> cat = jsonDecode(value); Map<String, dynamic> cat = jsonDecode(value);
//print(catData); //print(catData);
await cacheDb.rawInsert( await cacheDb.rawInsert("INSERT OR REPLACE INTO Categories (${Category.colCatId},${Category.colName},${Category.colProductive},${Category.colColor}) "
"INSERT OR REPLACE INTO Categories (${Category.colCatId},${Category "VALUES ('${cat['category_id']}','${cat['name']}',${cat['productive']},'${cat['color']}') ");
.colName},${Category.colProductive},${Category.colColor}) "
"VALUES ('${cat['category_id']}','${cat['name']}',${cat['productive']},'${cat['color']}') ");
} }
}catch(e){ } catch (e) {
offline=true; print("Error while cats $e");
} }
} }
Future<List<TaskType>> GetTaskTypes(bool forceOffline) async{ Future<List<TaskType>> GetTaskTypes(bool forceOffline) async {
List<TaskType> _taskTypes = []; List<TaskType> _taskTypes = [];
if(offline || forceOffline){ if (offline || forceOffline) {
//Retreive from cacheDB //Retreive from cacheDB
}else{ } else {
//Check if server got updated, If not go for cache //Check if server got updated, If not go for cache
var android_id = await Settings.UUID(); var android_id = await Settings.UUID();
bool updated =true; bool updated = true;
try{ try {
//Validate device_id to check updates //Validate device_id to check updates
http.Response update_response = (await http.post( http.Response update_response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/check_update.php'), body: <String, String>{"username": username, "device_id": android_id}));
Uri.parse('http://161.97.127.136/task_tracker/check_update.php'),
body: <String, String>{"username": username, "device_id":android_id}));
final data = update_response.body.split(','); final data = update_response.body.split(',');
updated = data[1] == '1'; updated = data[1] == '1';
}catch(e){ } catch (e) {
print(e); print(e);
} }
print("Need to update : ${!updated}"); print("Need to update : ${!updated}");
//Update CacheDB //Update CacheDB
// if(!updated){ if(!updated){
await UpdateTaskTypesFromServer(); await UpdateTaskTypesFromServer();
// } }
} }
await Future.delayed(Duration(seconds: 1)); await Future.delayed(Duration(seconds: 1));
List<Map> cats = await cacheDb.query('TaskTypes'); List<Map> cats = await cacheDb.query('TaskTypes');
print(cats.length); print(cats.length);
for(Map element in cats){ for (Map element in cats) {
String? id = element[TaskType.colId].toString(); String? id = element[TaskType.colId].toString();
String? name = element[TaskType.colName].toString(); String? name = element[TaskType.colName].toString();
String? category = element[TaskType.colCategory].toString(); String? category = element[TaskType.colCategory].toString();
Category? cat = await getCatFromId(category); Category? cat = await getCatFromId(category);
if(id==null || name==null || category==null){ if (id == null || name == null || category == null) {
print("Something is null!"); print("Something is null!");
print("name:{$name}, cat:{$category}, prod:{$id}"); print("name:{$name}, cat:{$category}, prod:{$id}");
continue; continue;
} }
print("name:{$name}, cat:{$category}, prod:{$id}"); // print("name:{$name}, cat:{$category}, prod:{$id}");
_taskTypes.add(TaskType(id,name,category,cat)); _taskTypes.add(TaskType(id, name, category, cat));
} }
taskTypes = _taskTypes; taskTypes = _taskTypes;
return taskTypes; return taskTypes;
} }
Future<void> UpdateTaskTypesFromServer() async{ Future<void> UpdateTaskTypesFromServer() async {
// await GetCategories(true);
// await GetCategories(true);
print("Updating TaskTypes as $username"); print("Updating TaskTypes as $username");
try { try {
http.Response response = (await http.post( http.Response response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/get_taskTypes.php'), body: <String, String>{"username": username, "device_id": await Settings.UUID()}));
Uri.parse('http://161.97.127.136/task_tracker/get_taskTypes.php'),
body: <String, String>{
"username": username,
"device_id": await Settings.UUID()
}));
print(response.body); print(response.body);
List<String> data = response.body.split("<td>"); List<String> data = response.body.split("<td>");
await cacheDb.delete("TaskTypes"); await cacheDb.delete("TaskTypes");
for (var value in data) { for (var value in data) {
Map<String, dynamic> cat = jsonDecode(value); Map<String, dynamic> cat = jsonDecode(value);
print(cat); //print(cat);
await cacheDb.rawInsert( await cacheDb.rawInsert("INSERT OR REPLACE INTO TaskTypes (${TaskType.colId},${TaskType.colName},${TaskType.colCategory}) "
"INSERT OR REPLACE INTO TaskTypes (${TaskType.colId},${TaskType "VALUES ('${cat['task_id']}','${cat['name']}','${cat['category_id']}') ");
.colName},${TaskType.colCategory}) "
"VALUES ('${cat['task_id']}','${cat['name']}','${cat['category_id']}') ");
print(await cacheDb.query("TaskTypes")); print(await cacheDb.query("TaskTypes"));
} }
}catch(e){ } catch (e) {
offline=true; print("Error while tasks $e");
} }
} }
Future<List<Activity>> GetActivities(bool forceOffline) async{ Future<List<Activity>> GetActivities(bool forceOffline) async {
List<Activity> _activities = []; List<Activity> _activities = [];
if(offline || forceOffline){ if (offline || forceOffline) {
//Retreive from cacheDB //Retreive from cacheDB
print('offline, refreshing activities'); print('offline, refreshing activities');
}else{ } else {
//Check if server got updated, If not go for cache //Check if server got updated, If not go for cache
var android_id = await Settings.UUID(); var android_id = await Settings.UUID();
bool updated =true; bool updated = true;
try{ try {
//Validate device_id to check updates //Validate device_id to check updates
http.Response update_response = (await http.post( http.Response update_response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/check_update.php'), body: <String, String>{"username": username, "device_id": android_id}));
Uri.parse('http://161.97.127.136/task_tracker/check_update.php'),
body: <String, String>{"username": username, "device_id":android_id}));
final data = update_response.body.split(','); final data = update_response.body.split(',');
updated = data[2] == '1'; updated = data[2] == '1';
}catch(e){ } catch (e) {
print(e); print(e);
} }
print("Need to update activities : ${!updated}"); print("Need to update activities : ${!updated}");
//Update CacheDB //Update CacheDB
//if(!updated){ if(!updated){
await UpdateActivitiesFromServer(); await UpdateActivitiesFromServer();
//} }
} }
List<Map> cats = await cacheDb.rawQuery('SELECT * FROM Activities ORDER BY ${Activity.colStartTime} DESC'); List<Map> cats = await cacheDb.rawQuery('SELECT * FROM Activities ORDER BY ${Activity.colStartTime} DESC');
print(cats.length); print(cats.length);
for(Map element in cats){ for (Map element in cats) {
String? type = element[Activity.colType].toString(); String? type = element[Activity.colType].toString();
String? startTime = element[Activity.colStartTime].toString(); String? startTime = element[Activity.colStartTime].toString();
String? endTime = element[Activity.colEndTime].toString(); String? endTime = element[Activity.colEndTime].toString();
String? metadata = element[Activity.colMetadata].toString(); String? metadata = element[Activity.colMetadata].toString();
TaskType? taskType = await getTaskFromId(type); TaskType? taskType = await getTaskFromId(type);
if(type==null || startTime==null || endTime==null || taskType==null){ 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("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}"); print("TaskType:{$type}, Start Time:{$startTime}, endTime:{$endTime}, metadata:${metadata}");
continue; continue;
} }
print("TaskType:{$type}, Start Time:{$startTime}, endTime:{$endTime}, metadata:${metadata}"); //print("TaskType:{$type}, Start Time:{$startTime}, endTime:{$endTime}, metadata:${metadata}");
_activities.add(Activity(taskType, DateTime.parse(startTime), DateTime.parse(endTime), metadata: metadata)); _activities.add(Activity(taskType, DateTime.parse(startTime), DateTime.parse(endTime), metadata: metadata));
} }
activities = _activities; activities = _activities;
return activities; return activities;
} }
Future<void> UpdateActivitiesFromServer() async{ Future<void> UpdateActivitiesFromServer() async {
print("Updating Activities as $username"); print("Updating Activities as $username");
try { try {
http.Response response = (await http.post( http.Response response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/get_activities.php'), body: <String, String>{"username": username, "device_id": await Settings.UUID()}));
Uri.parse('http://161.97.127.136/task_tracker/get_activities.php'),
body: <String, String>{
"username": username,
"device_id": await Settings.UUID()
}));
await cacheDb.rawDelete("DELETE FROM Activities"); await cacheDb.rawDelete("DELETE FROM Activities");
print('Truncate Activity Table before'); print('Truncate Activity Table before');
print("Activity response: ${response.body}"); print("Activity response: ${response.body}");
if(response.body.contains("{")){ if (response.body.contains("{")) {
List<String> data = response.body.split("<td>"); List<String> data = response.body.split("<td>");
for (var value in data){ for (var value in data) {
Map<String, dynamic> cat = jsonDecode(value); Map<String, dynamic> cat = jsonDecode(value);
print(cat); //print(cat);
await cacheDb.rawInsert( await cacheDb.rawInsert("INSERT OR REPLACE INTO Activities (${Activity.colType}, ${Activity.colStartTime}, ${Activity.colEndTime}, ${Activity.colMetadata}) "
"INSERT OR REPLACE INTO Activities (${Activity.colType}, ${Activity.colStartTime}, ${Activity.colEndTime}, ${Activity.colMetadata}) " "VALUES ('${cat['task_id']}', '${cat['sTime']}','${cat['eTime']}', '${cat['metadata']}') ");
"VALUES ('${cat['task_id']}', '${cat['sTime']}','${cat['eTime']}', '${cat['metadata']}') "); }
} } else {
}else{
print("No activities for now"); print("No activities for now");
} }
}catch(e){ } catch (e) {
print("Error : $e @ updating activities"); print("Error : $e @ updating activities");
offline=true; print("Error while acts $e");
} }
} }
Future<TaskType?> getTaskFromId(String taskId) async{ Future<TaskType?> getTaskFromId(String taskId) async {
// await GetTaskTypes(false); // await GetTaskTypes(false);
TaskType? cat = null; TaskType? cat = null;
for (var element in taskTypes){ for (var element in taskTypes) {
if(element.id == taskId){ if (element.id == taskId) {
cat= element; cat = element;
cat?.cat = await getCatFromId((cat?.category ?? '')); cat?.cat = await getCatFromId((cat?.category ?? ''));
} }
} }
if(cat==null){ if (cat == null) {
print('Got null tasktype for ${taskId} after searching on ${taskTypes.length}'); print('Got null tasktype for ${taskId} after searching on ${taskTypes.length}');
} }
return cat; return cat;
} }
Future<Category?> getCatFromId(String catId) async{ Future<Category?> getCatFromId(String catId) async {
// await GetTaskTypes(false); // await GetTaskTypes(false);
Category? cat = null; Category? cat = null;
for (var element in categories) { for (var element in categories) {
if(element.category_id == catId){ if (element.category_id == catId) {
cat= element; cat = element;
} }
} }
return cat; return cat;
} }
//Helpers //Helpers
class Helpers { class Helpers {
Future<String?> _getId() async { Future<String?> _getId() async {
var deviceInfo = DeviceInfoPlugin(); var deviceInfo = DeviceInfoPlugin();
if (Platform.isIOS) { // import 'dart:io' if (Platform.isIOS) {
// import 'dart:io'
var iosDeviceInfo = await deviceInfo.iosInfo; var iosDeviceInfo = await deviceInfo.iosInfo;
return iosDeviceInfo.identifierForVendor; // unique ID on iOS return iosDeviceInfo.identifierForVendor; // unique ID on iOS
} else { } else {
@@ -454,228 +410,185 @@ class Helpers {
} }
} }
} }
bool ParseBool(obj){
return obj.toString().toLowerCase()=="true" || obj.toString()=="1"; bool ParseBool(obj) {
return obj.toString().toLowerCase() == "true" || obj.toString() == "1";
} }
class UserOperations {
static DateFormat dFormat = DateFormat("yyyy-MM-dd HH:mm:ss");
class UserOperations{ static Future<void> addCategory(String name, String color, bool productive, {bool bulk = false}) async {
static Future<void> addCategory(String name, String color, bool productive, {bool bulk = false}) async{ Map<String, String> queryBody = <String, String>{'username': username, 'device_id': await Settings.UUID(), 'name': name, 'color': color, 'productive': productive ? '1' : '0'};
Map<String,String> queryBody= <String,String>{
'username': username,
'device_id': await Settings.UUID(),
'name' : name,
'color':color,
'productive': productive ? '1':'0'
};
//Add Query //Add Query
Map<String,Object> query = { Map<String, Object> query = {Queries.colLink: 'add_category', Queries.colData: jsonEncode(queryBody)};
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<String,Object> data = {
Category.colCatId: username+name,
Category.colName: name,
Category.colColor: color,
Category.colProductive: productive
};
await cacheDb.insert('Categories', data);
await GetCategories(true);
if(!bulk){
//Add to server and refresh Cache
await executeQueries();
}
}
static Future<void> addTaskType(String name, String category, {bool bulk = false}) async{
Map<String,String> queryBody= <String,String>{
'id':username+name,
'username': username,
'device_id': await Settings.UUID(),
'name' : name,
'category': username + category
};
//Add Query
Map<String,Object> query = {
Queries.colLink: 'add_taskType',
Queries.colData: jsonEncode(queryBody)
};
print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}");
await cacheDb.insert('Queries', query); await cacheDb.insert('Queries', query);
//update Cache //update Cache
Map<String,Object> data = { Map<String, Object> data = {Category.colCatId: username + name, Category.colName: name, Category.colColor: color, Category.colProductive: productive};
TaskType.colId: username+name, await cacheDb.insert('Categories', data);
Category.colName: name, await refreshUserData();
Category.colCatId: username + category if (!bulk) {
};
await cacheDb.insert('TaskTypes', data);
await GetTaskTypes(true);
if(!bulk){
//Add to server and refresh Cache //Add to server and refresh Cache
await executeQueries(); await executeQueries();
} }
} }
static Future<void> addActivity(String type, String sTime,String eTime, {String metadata = 'null',bool bulk = false, Function(int)? onOverlap}) async{ static Future<void> addTaskType(String name, String category, {bool bulk = false}) async {
Map<String, String> queryBody = <String, String>{'id': username + name, 'username': username, 'device_id': await Settings.UUID(), 'name': name, 'category': username + category};
//Add Query
Map<String, Object> 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<String, Object> 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<void> addActivity(String type, DateTime sTime, DateTime eTime, {String metadata = 'null', bool bulk = false, Function(int)? onOverlap}) async {
//Check for timeoverlapse //Check for timeoverlapse
activities= await GetActivities(true); 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')))")); 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"); print("ActivityOverlaps: $overlapCount");
if(overlapCount! > 0){ if (overlapCount! > 0) {
onOverlap!(overlapCount); onOverlap!(overlapCount);
return;
} }
Map<String,String> queryBody= <String,String>{ Map<String, String> queryBody = <String, String>{
'username': username, 'username': username,
'device_id': await Settings.UUID(), 'device_id': await Settings.UUID(),
'type' : username+type, 'type': username + type,
'sTime': sTime, 'sTime': dFormat.format(sTime),
'eTime':eTime, 'eTime': dFormat.format(eTime),
'metadata': metadata 'metadata': metadata
}; };
if(metadata.length > 0){ if (metadata.length > 0) {}
}
//Add Query //Add Query
Map<String,Object> query = { Map<String, Object> query = {Queries.colLink: 'add_activity', Queries.colData: jsonEncode(queryBody)};
Queries.colLink: 'add_activity',
Queries.colData: jsonEncode(queryBody)
};
print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}");
await cacheDb.insert('Queries', query); await cacheDb.insert('Queries', query);
//update Cache //update Cache
Map<String,Object> data = { Map<String, Object> data = {Activity.colType: username + type, Activity.colStartTime: dFormat.format(sTime), Activity.colEndTime: dFormat.format(eTime), Activity.colMetadata: metadata};
Activity.colType: username+type,
Activity.colStartTime: sTime,
Activity.colEndTime: eTime,
Activity.colMetadata: metadata
};
await cacheDb.insert('Activities', data); await cacheDb.insert('Activities', data);
activities= await GetActivities(false); await refreshUserData();
if(!bulk){ if (!bulk) {
//Add to server and refresh Cache //Add to server and refresh Cache
await executeQueries(); await executeQueries();
} }
} }
static Future<void> deleteTask(String name,{bulk=false}) async{ static Future<void> deleteTask(String name, {bulk = false}) async {
Map<String,String> queryBody= <String,String>{ Map<String, String> queryBody = <String, String>{
'id':username+name, 'id': username + name,
'username': username, 'username': username,
'device_id': await Settings.UUID(), 'device_id': await Settings.UUID(),
}; };
//Add Query //Add Query
Map<String,Object> query = { Map<String, Object> query = {Queries.colLink: 'delete_taskType', Queries.colData: jsonEncode(queryBody)};
Queries.colLink: 'delete_taskType',
Queries.colData: jsonEncode(queryBody)
};
print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}");
await cacheDb.insert('Queries', query); await cacheDb.insert('Queries', query);
//update Cache //update Cache
Map<String,Object> data = { Map<String, Object> data = {
TaskType.colId: username+name, TaskType.colId: username + name,
Category.colName: name, Category.colName: name,
}; };
await cacheDb.rawDelete("DELETE FROM TaskTypes WHERE id='${username+name}'"); await cacheDb.rawDelete("DELETE FROM TaskTypes WHERE id='${username + name}'");
await GetTaskTypes(true);
//Add to server and refresh Cache
if(!bulk) { await refreshUserData();
await executeQueries();
}
}
static Future<void> deleteCategory(String name,{bulk=false}) async{
Map<String,String> queryBody= <String,String>{
'id':username+name,
'username': username,
'device_id': await Settings.UUID() ,
};
//Add Query
Map<String,Object> 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<String,Object> data = {
TaskType.colId: username+name,
Category.colName: name,
};
await cacheDb.rawDelete("DELETE FROM Categories WHERE ${Category.colCatId}='${username+name}'");
await GetCategories(true);
//Add to server and refresh Cache //Add to server and refresh Cache
if(!bulk) { if (!bulk) {
await executeQueries(); await executeQueries();
} }
} }
static Future<void> deleteActivity(Activity activity,{bulk=false}) async{ static Future<void> deleteCategory(String name, {bulk = false}) async {
Map<String,String> queryBody= <String,String>{ Map<String, String> queryBody = <String, String>{
'id': username + name,
'username': username,
'device_id': await Settings.UUID(),
};
//Add Query
Map<String, Object> 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<String, Object> 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<void> deleteActivity(Activity activity, {bulk = false}) async {
Map<String, String> queryBody = <String, String>{
'username': username, 'username': username,
'device_id': await Settings.UUID(), 'device_id': await Settings.UUID(),
'sTime': activity.startTime.toString(), 'sTime': activity.startTime.toString(),
'eTime':activity.endTime.toString(), 'eTime': activity.endTime.toString(),
}; };
//Add Query //Add Query
Map<String,Object> query = { Map<String, Object> query = {Queries.colLink: 'delete_activity', Queries.colData: jsonEncode(queryBody)};
Queries.colLink: 'delete_activity',
Queries.colData: jsonEncode(queryBody)
};
print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}");
await cacheDb.insert('Queries', query); await cacheDb.insert('Queries', query);
//update Cache //update Cache
// Map<String,Object> data = { String deleteQuery =
// TaskType.colId: username+name, "DELETE FROM Activities WHERE ${Activity.colStartTime}=datetime('${dFormat.format(activity.startTime)}') AND ${Activity.colEndTime}=datetime('${dFormat.format(activity.endTime)}')";
// Category.colName: name, print("delteQuery : $deleteQuery");
// };
// await cacheDb.rawDelete("DELETE FROM Categories WHERE ${Category.colCatId}='${username+name}'"); await cacheDb.rawDelete(deleteQuery);
await GetCategories(true); await refreshUserData();
//Add to server and refresh Cache //Add to server and refresh Cache
if(!bulk) { if (!bulk) {
await executeQueries(); await executeQueries();
} }
} }
static Future<void> executeQueries() async{ static Future<void> executeQueries() async {
if(offline){ if (offline) {
print("Cannot executre queries, Offline!"); print("Cannot executre queries, Offline!");
return; return;
} }
ShowProgress("Syncing"); List<Map<String, Object?>> queries = await cacheDb.query('Queries');
List<Map<String,Object?>> queries = await cacheDb.query('Queries');
for(Map<String,Object?> element in queries){ for (Map<String, Object?> element in queries) {
int id = int.parse(element['id'].toString()); int id = int.parse(element['id'].toString());
String? file = element[Queries.colLink].toString(); String? file = element[Queries.colLink].toString();
String? data = element[Queries.colData].toString(); String? data = element[Queries.colData].toString();
if(file==null || data==null){ if (file == null || data == null) {
print("Null query, Ignoring..."); print("Null query, Ignoring...");
continue; continue;
} }
@@ -684,27 +597,16 @@ class UserOperations{
//Execute the http here //Execute the http here
Map<String, dynamic> body = jsonDecode(data); Map<String, dynamic> body = jsonDecode(data);
try { try {
http.Response queryResponse = (await http.post( http.Response queryResponse = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/$file.php'), body: body));
Uri.parse('http://161.97.127.136/task_tracker/$file.php'),
body: body));
print("Query executed : Results{${queryResponse.body}"); print("Query executed : Results{${queryResponse.body}");
if (queryResponse.body.toLowerCase().contains("success")) { if (queryResponse.body.toLowerCase().contains("success")) {
await cacheDb.rawDelete('DELETE FROM Queries WHERE id=$id'); await cacheDb.rawDelete('DELETE FROM Queries WHERE id=$id');
} }
offline=false; offline = false;
}catch(e){ } catch (e) {
offline=true; print("Error while query $e");
} }
} }
HideProgress(); await refreshUserData();
} }
} }
void ShowProgress(msg){
//try{progressDialog?.show(max: 100, msg: msg);}catch(e){}
}
void HideProgress(){
// try{progressDialog?.update(value: 100);}catch(e){}
}

View File

@@ -392,7 +392,7 @@ class _onlineLoginPageState extends State<onlineLoginPage>
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
prefs.setString("username", usernameController.text); prefs.setString("username", usernameController.text);
prefs.setString("password", passwordController.text); prefs.setString("password", passwordController.text);
Navigator.of(context).pushNamedAndRemoveUntil('/splash', (route) => false); Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false);
}else{ }else{
showAlertDialog(context, "Failed to login", "There was an error trying to authorize you in servers."); showAlertDialog(context, "Failed to login", "There was an error trying to authorize you in servers.");
} }

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@@ -14,11 +16,10 @@ import 'newActivity.dart';
import 'Tasks.dart'; import 'Tasks.dart';
import 'Activities.dart'; import 'Activities.dart';
import 'User.dart' as User; import 'User.dart' as User;
import 'package:sn_progress_dialog/sn_progress_dialog.dart';
import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:syncfusion_flutter_charts/charts.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
late ProgressDialog progressDialog; import 'Dialogs.dart';
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
showAlertDialog(BuildContext context, String title, String message) { showAlertDialog(BuildContext context, String title, String message) {
// set up the button // set up the button
Widget okButton = TextButton( Widget okButton = TextButton(
@@ -73,27 +74,28 @@ class MyApp extends StatelessWidget {
// This widget is the root of your application. // This widget is the root of your application.
@override @override
Widget build(BuildContext context) => ChangeNotifierProvider( Widget build(BuildContext context) => ChangeNotifierProvider(
create: (context)=>ThemeProvider(), create: (context) => ThemeProvider(),
builder: (context, _){ builder: (context, _) {
final themeProvider = Provider.of<ThemeProvider>(context); final themeProvider = Provider.of<ThemeProvider>(context);
return MaterialApp( return MaterialApp(
title: 'Task Tracker', title: 'Task Tracker',
themeMode: themeProvider.themeMode, themeMode: themeProvider.themeMode,
theme: ThemeData(accentColor: Colors.redAccent,brightness: Brightness.light, primaryColor: Colors.amber, fontFamily: 'Noto-Sans'), theme: ThemeData(accentColor: Colors.redAccent, brightness: Brightness.light, primaryColor: Colors.amber, fontFamily: 'Noto-Sans'),
darkTheme: ThemeData(accentColor: Colors.redAccent,brightness: Brightness.dark, primaryColor: Colors.amber, fontFamily: 'Noto-Sans'), darkTheme: ThemeData(accentColor: Colors.redAccent, brightness: Brightness.dark, primaryColor: Colors.amber, fontFamily: 'Noto-Sans'),
//home: const MyHomePage(), navigatorKey: navigatorKey,
initialRoute: '/splash', //home: const SplashScreen(),
routes: { initialRoute: '/',
'/splash': (context) => const SplashScreen(), routes: {
'/welcome': (context) => const WelcomePage(), '/': (context) => const SplashScreen(),
'/': (context) => const MyHomePage(), '/welcome': (context) => const WelcomePage(),
'/Tasks': (context) => const Tasks(), '/home': (context) => const MyHomePage(),
'/Categories': (context) => const Categories(), '/Tasks': (context) => const Tasks(),
'/Activities': (context) => const Activities(), '/Categories': (context) => const Categories(),
'/Settings':(context) => const SettingsPage() '/Activities': (context) => const Activities(),
}); '/Settings': (context) => const SettingsPage()
}); });
});
} }
List<String> days = []; List<String> days = [];
@@ -135,24 +137,48 @@ class MyHomePage extends StatefulWidget {
} }
class _MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> {
var connectivitySub;
@override @override
void initState() { void initState() {
// TODO: implement initState // TODO: implement initState
print("Im home!");
init(context);
super.initState(); super.initState();
// User.refreshUserData().then((val) => LoadStats()); print("Initializing refresh stream on main dart");
// showOfflineSnack();
LoadStats(); LoadStats();
// progressDialog = ProgressDialog(context: context); connectivitySub=Connectivity().onConnectivityChanged.listen((result) {
if (this.mounted) {
setState(() {});
}
});
// User.progressDialog=progressDialog; // User.progressDialog=progressDialog;
} }
var refreshSub;
void init(BuildContext context) async{
await Future.delayed(Duration(seconds: 1));
refreshSub = User.refreshStream.stream.listen((value) {
print("Streaming refresh : $value");
if(value){
Dialogs.waiting("Syncing...");
print("Opening progress dialog");
}else{
Dialogs.hide();
print("Closing progress dialog");
}
});
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
connectivitySub?.cancel();
}
void LoadStats() async { void LoadStats() async {
// return; // return;
while (!User.userDataInitiated) {
await Future.delayed(const Duration(seconds: 1));
}
DateFormat dFormat = DateFormat("MM/dd"); DateFormat dFormat = DateFormat("MM/dd");
Map<Category, int> catTimeMap = <Category, int>{}; Map<Category, int> catTimeMap = <Category, int>{};
Map<Category, int> catBriefMap = <Category, int>{}; Map<Category, int> catBriefMap = <Category, int>{};
@@ -163,7 +189,7 @@ class _MyHomePageState extends State<MyHomePage> {
firstDay = null; firstDay = null;
lastDay = null; lastDay = null;
String lastDate = ""; String lastDate = "";
days=[]; days = [];
for (var element in User.activities) { for (var element in User.activities) {
if (lastDay == null) { if (lastDay == null) {
lastDay = element.endTime; lastDay = element.endTime;
@@ -248,7 +274,7 @@ class _MyHomePageState extends State<MyHomePage> {
Color barCol = HexColor.fromHex(key.color); Color barCol = HexColor.fromHex(key.color);
dailyData.add(CatMapData(key.name, value, barCol)); dailyData.add(CatMapData(key.name, value, barCol));
}); });
dailyData.sort((a,b){ dailyData.sort((a, b) {
return a.name.toLowerCase().compareTo(b.name.toLowerCase()); return a.name.toLowerCase().compareTo(b.name.toLowerCase());
}); });
for (var element in days) { for (var element in days) {
@@ -265,7 +291,7 @@ class _MyHomePageState extends State<MyHomePage> {
taskTypesData.add(TaskTypeMapData(key.name, value, HexColor.fromHex(key.cat!.color))); taskTypesData.add(TaskTypeMapData(key.name, value, HexColor.fromHex(key.cat!.color)));
}); });
taskTypesData.sort((a,b){ taskTypesData.sort((a, b) {
return a.time.compareTo(b.time); return a.time.compareTo(b.time);
}); });
@@ -274,7 +300,7 @@ class _MyHomePageState extends State<MyHomePage> {
Color barCol = HexColor.fromHex(key.color); Color barCol = HexColor.fromHex(key.color);
catsData.add(CatMapData(key.name, value, barCol)); catsData.add(CatMapData(key.name, value, barCol));
}); });
catsData.sort((a,b)=> a.time.compareTo(b.time)); catsData.sort((a, b) => a.time.compareTo(b.time));
}); });
} }
} }
@@ -299,26 +325,46 @@ class _MyHomePageState extends State<MyHomePage> {
}, },
label: Text("New Activity"), label: Text("New Activity"),
icon: Icon(Icons.add)), icon: Icon(Icons.add)),
appBar: AppBar(title: Row( appBar: AppBar(
mainAxisSize: MainAxisSize.max, title: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Row(children: [Icon(Icons.article_outlined, color: Theme.of(context).primaryColor), SizedBox(width: 10), Text('Summary')]), Row(
Row(children: [ mainAxisSize: MainAxisSize.max,
InkWell( mainAxisAlignment: MainAxisAlignment.spaceBetween,
onTap: () { children: [
setState(() { Row(children: [Icon(Icons.article_outlined, color: Theme.of(context).primaryColor), SizedBox(width: 10), Text('Summary')]),
LoadStats(); Row(
}); children: [
}, (User.offline)
child: Icon(Icons.refresh, size: 30), ? Icon(Icons.signal_cellular_connected_no_internet_4_bar_outlined)
) : InkWell(
],) onTap: () {
setState(() {
LoadStats();
});
},
child: Icon(Icons.refresh, size: 30),
)
],
)
],
),
//Container(color: Colors.red,child: Text("Offline",style:TextStyle(fontSize: 5))),
], ],
)), )),
drawer: navDrawer(context, 0), drawer: navDrawer(context, 0),
body: SafeArea( body: SafeArea(
child: SingleChildScrollView( child: (User.activities.isEmpty) ? Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center
,children:[
Expanded(flex: 1,child: Container(),),
Expanded(flex: 2,child: Image(image: AssetImage('images/empty.png'))),
Expanded(flex:2,child: Text("Add your first activity to access Summary",style: TextStyle(color: Colors.grey, fontStyle: FontStyle.italic),))
]) :SingleChildScrollView(
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
child: Column( child: Column(
children: [ children: [
@@ -397,11 +443,7 @@ class _MyHomePageState extends State<MyHomePage> {
xValueMapper: (ProductivityMapData sales, _) => sales.day, xValueMapper: (ProductivityMapData sales, _) => sales.day,
yValueMapper: (ProductivityMapData sales, _) => sales.productivity, yValueMapper: (ProductivityMapData sales, _) => sales.productivity,
dataLabelMapper: (ProductivityMapData sales, _) => sales.productivity.toStringAsFixed(1) + "%", dataLabelMapper: (ProductivityMapData sales, _) => sales.productivity.toStringAsFixed(1) + "%",
dataLabelSettings: DataLabelSettings( dataLabelSettings: DataLabelSettings(overflowMode: OverflowMode.hide, showZeroValue: false, isVisible: true),
overflowMode: OverflowMode.hide,
showZeroValue: false,
isVisible: true
),
color: Colors.green) color: Colors.green)
]), ]),
) )
@@ -602,7 +644,7 @@ Drawer navDrawer(BuildContext context, int pageIndex) {
if (pageIndex == 0) { if (pageIndex == 0) {
return; return;
} }
Navigator.of(context).pushReplacementNamed('/'); Navigator.of(context).pushReplacementNamed('/home');
}, },
), ),
// ListTile( // ListTile(
@@ -652,6 +694,18 @@ Drawer navDrawer(BuildContext context, int pageIndex) {
}, },
), ),
Divider(), Divider(),
ListTile(
selected: (pageIndex == 7),
title: Text('TODO'),
leading: Icon(Icons.check, color: Theme.of(context).primaryColor),
onTap: () {
if (pageIndex == 7) {
return;
}
Navigator.of(context).pushReplacementNamed('/Todo');
},
),
Divider(),
ListTile( ListTile(
selected: (pageIndex == 5), selected: (pageIndex == 5),
title: Text('Settings'), title: Text('Settings'),
@@ -667,7 +721,9 @@ Drawer navDrawer(BuildContext context, int pageIndex) {
selected: (pageIndex == 6), selected: (pageIndex == 6),
title: Text('About'), title: Text('About'),
leading: Icon(Icons.help_outline_outlined), leading: Icon(Icons.help_outline_outlined),
onTap: () {}, onTap: () {
showAboutDialog(context: context);
},
), ),
], ],
)); ));

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:tasktracker/main.dart';
import 'User.dart' as User; import 'User.dart' as User;
DateFormat dateFormat = DateFormat("yyyy-MM-dd HH:mm:ss"); DateFormat dateFormat = DateFormat("yyyy-MM-dd HH:mm:ss");
DateFormat durationFormat = DateFormat("HH:mm:ss"); DateFormat durationFormat = DateFormat("HH:mm:ss");
@@ -292,16 +293,21 @@ class _NewActivity extends State<NewActivity> {
} }
void add_action() async{ void add_action() async{
print('adding Task Type : $selectedCat at $startTime - $endTime'); print('adding Task Type : $selectedCat at $startTime - $endTime');
bool failed=false; bool failed=false;
await User.UserOperations.addActivity(selectedCat,startTime.toString(), endTime.toString(),metadata:metadataController.text, onOverlap: (overlapCount){ await User.UserOperations.addActivity(selectedCat,startTime, endTime,metadata:metadataController.text, onOverlap: (overlapCount){
showAlertDialog(context, 'Error adding activity', 'Cannot add activity between ${dateFormat.format(startTime)} - ${dateFormat.format(endTime)}, $overlapCount activities are already added within this time range'); showAlertDialog(context, 'Error adding activity', 'Cannot add activity between ${dateFormat.format(startTime)} - ${dateFormat.format(endTime)}, $overlapCount activities are already added within this time range');
failed=true; failed=true;
}); });
if(!failed) if(!failed) {
Navigator.of(context).pop(); print("popping : ${navigatorKey.currentWidget?.toStringShort() ?? "n/a"}");
Navigator.of(navigatorKey.currentContext!).popUntil((route){
return route.isFirst;
});
}else{
print("Failed adding new activity");
}
} }
} }

View File

@@ -1,12 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:tasktracker/Data.dart'; import 'package:tasktracker/Data.dart';
import 'User.dart' as Users; import 'User.dart' as Users;
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:sn_progress_dialog/sn_progress_dialog.dart';
import 'theme_provider.dart'; import 'theme_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
late ProgressDialog progressDialog; import 'newActivity.dart';
class SplashScreen extends StatefulWidget { class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key); const SplashScreen({Key? key}) : super(key: key);
@@ -31,11 +32,41 @@ class _SplashScreenState extends State<SplashScreen> {
void initState() { void initState() {
// TODO: implement initState // TODO: implement initState
super.initState(); super.initState();
progressDialog = ProgressDialog(context: context);
Users.progressDialog=progressDialog;
init(); init();
initNotifications();
} }
void initNotifications() async{
var androidInitilize = new AndroidInitializationSettings('@mipmap/ic_launcher');
var iOSinitilize = new IOSInitializationSettings();
var initilizationsSettings =
new InitializationSettings(android: androidInitilize, iOS: iOSinitilize);
var fltrNotification = new FlutterLocalNotificationsPlugin();
fltrNotification.initialize(initilizationsSettings,
onSelectNotification: notificationSelected);
int notification_interval = await Settings.getNotificationInterval();
if(notification_interval>0) {
var androidDetails = const AndroidNotificationDetails("Xperience", "TaskTracker", importance: Importance.max);
var iSODetails = new IOSNotificationDetails();
var generalNotificationDetails =
new NotificationDetails(android: androidDetails, iOS: iSODetails);
var scheduledTime = DateTime.now().add(Duration(hours: notification_interval));
fltrNotification.schedule(1, "What did you do in last $notification_interval hours?", "Click here to track your last activities...",
scheduledTime, generalNotificationDetails,);
print("Sent notification schedule");
}
}
void notificationSelected(String? payload) {
if(payload!=null){
if(payload.toLowerCase().contains("activity")){
Navigator.of(context).push(MaterialPageRoute(builder: (context) => NewActivity())).then((value) => {Users.refreshUserData()});
}
}
}
void init() async { void init() async {
await initSettings(); await initSettings();
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
@@ -73,20 +104,24 @@ class _SplashScreenState extends State<SplashScreen> {
void Continue() async{ void Continue() async{
await Users.initUserData(); await Users.initUserData();
Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false); Navigator.of(context).pushReplacementNamed('/home');
print('Going home!');
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
color: Colors.purple, color: Colors.redAccent,
padding: EdgeInsets.all(80), padding: EdgeInsets.all(80),
child: Column( child: Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Image(image: AssetImage('images/logo.png')), Image(image: AssetImage('images/logo.png')),
SpinKitPouringHourGlass(color: Colors.white)
// Text('Loading', style:TextStyle(color: Colors.grey, fontSize: 20,fontStyle: FontStyle.italic)) // Text('Loading', style:TextStyle(color: Colors.grey, fontSize: 20,fontStyle: FontStyle.italic))
])); ]));
} }
} }

1
linux/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
flutter/ephemeral

116
linux/CMakeLists.txt Normal file
View File

@@ -0,0 +1,116 @@
cmake_minimum_required(VERSION 3.10)
project(runner LANGUAGES CXX)
set(BINARY_NAME "tasktracker")
set(APPLICATION_ID "com.Xperience.TaskTracker.tasktracker")
cmake_policy(SET CMP0063 NEW)
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Configure build options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
# Flutter library and tool build rules.
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Application build
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
apply_standard_settings(${BINARY_NAME})
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()

View File

@@ -0,0 +1,87 @@
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)

View File

@@ -0,0 +1,11 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void fl_register_plugins(FlPluginRegistry* registry) {
}

View File

@@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View File

@@ -0,0 +1,15 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)

6
linux/main.cc Normal file
View File

@@ -0,0 +1,6 @@
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}

104
linux/my_application.cc Normal file
View File

@@ -0,0 +1,104 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "tasktracker");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "tasktracker");
}
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
nullptr));
}

18
linux/my_application.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_

View File

@@ -1,6 +1,13 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -43,6 +50,48 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.15.0" version: "1.15.0"
connectivity_plus:
dependency: "direct main"
description:
name: connectivity_plus
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1"
connectivity_plus_linux:
dependency: transitive
description:
name: connectivity_plus_linux
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
connectivity_plus_macos:
dependency: transitive
description:
name: connectivity_plus_macos
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
connectivity_plus_web:
dependency: transitive
description:
name: connectivity_plus_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
connectivity_plus_windows:
dependency: transitive
description:
name: connectivity_plus_windows
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@@ -57,6 +106,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
dbus:
dependency: transitive
description:
name: dbus
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.1"
device_info_plus: device_info_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -146,6 +202,34 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
url: "https://pub.dartlang.org"
source: hosted
version: "9.3.3"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.2"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.0"
flutter_spinkit:
dependency: "direct main"
description:
name: flutter_spinkit
url: "https://pub.dartlang.org"
source: hosted
version: "5.1.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -219,6 +303,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
nm:
dependency: transitive
description:
name: nm
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@@ -275,6 +366,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.5" version: "2.0.5"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "4.4.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@@ -303,6 +401,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.2" version: "6.0.2"
rxdart:
dependency: "direct main"
description:
name: rxdart
url: "https://pub.dartlang.org"
source: hosted
version: "0.27.3"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -364,13 +469,6 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.99" version: "0.0.99"
sn_progress_dialog:
dependency: "direct main"
description:
name: sn_progress_dialog
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@@ -448,6 +546,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.8" version: "0.4.8"
timezone:
dependency: transitive
description:
name: timezone
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.0"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -518,6 +623,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0+1" version: "0.2.0+1"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "5.3.1"
sdks: sdks:
dart: ">=2.16.0 <3.0.0" dart: ">=2.16.0 <3.0.0"
flutter: ">=2.8.0" flutter: ">=2.8.0"

View File

@@ -30,6 +30,10 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
connectivity_plus: ^2.2.1
flutter_spinkit: ^5.1.0
flutter_local_notifications: ^9.3.3
rxdart: ^0.27.1
provider: ^6.0.2 provider: ^6.0.2
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
uuid: ^3.0.5 uuid: ^3.0.5
@@ -39,11 +43,10 @@ dependencies:
sqflite: ^2.0.2 sqflite: ^2.0.2
intl: ^0.17.0 intl: ^0.17.0
flutter_colorpicker: ^1.0.3 flutter_colorpicker: ^1.0.3
path_provider: ^2.0.9 path_provider: ^2.0.0
shared_preferences: ^2.0.13 shared_preferences: ^2.0.13
http: ^0.13.4 http: ^0.13.4
device_info_plus: ^3.2.1 device_info_plus: ^3.2.1
sn_progress_dialog: ^1.0.3
flutter_lints: ^1.0.0 flutter_lints: ^1.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

BIN
web/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

BIN
web/icons/Icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
web/icons/Icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

104
web/index.html Normal file
View File

@@ -0,0 +1,104 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="tasktracker">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>tasktracker</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
scriptTag.src = 'main.dart.js';
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing || reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.',
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</script>
</body>
</html>

35
web/manifest.json Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "tasktracker",
"short_name": "tasktracker",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

17
windows/.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
flutter/ephemeral/
# Visual Studio user-specific files.
*.suo
*.user
*.userosscache
*.sln.docstates
# Visual Studio build-related files.
x64/
x86/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

95
windows/CMakeLists.txt Normal file
View File

@@ -0,0 +1,95 @@
cmake_minimum_required(VERSION 3.14)
project(tasktracker LANGUAGES CXX)
set(BINARY_NAME "tasktracker")
cmake_policy(SET CMP0063 NEW)
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Configure build options.
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(IS_MULTICONFIG)
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
endif()
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
# Use Unicode for all projects.
add_definitions(-DUNICODE -D_UNICODE)
# Compilation settings that should be applied to most targets.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
target_compile_options(${TARGET} PRIVATE /EHsc)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
endfunction()
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
# Flutter library and tool build rules.
add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build
add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# Support files are copied into place next to the executable, so that it can
# run in place. This is done instead of making a separate bundle (as on Linux)
# so that building and running from within Visual Studio will work.
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
# Make the "install" step default, as it's required to run.
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
CONFIGURATIONS Profile;Release
COMPONENT Runtime)

View File

@@ -0,0 +1,103 @@
cmake_minimum_required(VERSION 3.14)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_windows.h"
"flutter_messenger.h"
"flutter_plugin_registrar.h"
"flutter_texture_registrar.h"
)
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"core_implementations.cc"
"standard_codec.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc"
"flutter_view_controller.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_app flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
${PHONY_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
windows-x64 $<CONFIG>
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)

View File

@@ -0,0 +1,14 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <connectivity_plus_windows/connectivity_plus_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
}

View File

@@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View File

@@ -0,0 +1,16 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus_windows
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)

View File

@@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.14)
project(runner LANGUAGES CXX)
add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp"
"main.cpp"
"utils.cpp"
"win32_window.cpp"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
apply_standard_settings(${BINARY_NAME})
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
add_dependencies(${BINARY_NAME} flutter_assemble)

121
windows/runner/Runner.rc Normal file
View File

@@ -0,0 +1,121 @@
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APP_ICON ICON "resources\\app_icon.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
#ifdef FLUTTER_BUILD_NUMBER
#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
#else
#define VERSION_AS_NUMBER 1,0,0
#endif
#ifdef FLUTTER_BUILD_NAME
#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
#else
#define VERSION_AS_STRING "1.0.0"
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_AS_NUMBER
PRODUCTVERSION VERSION_AS_NUMBER
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.Xperience.TaskTracker" "\0"
VALUE "FileDescription", "tasktracker" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "tasktracker" "\0"
VALUE "LegalCopyright", "Copyright (C) 2022 com.Xperience.TaskTracker. All rights reserved." "\0"
VALUE "OriginalFilename", "tasktracker.exe" "\0"
VALUE "ProductName", "tasktracker" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@@ -0,0 +1,61 @@
#include "flutter_window.h"
#include <optional>
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
FlutterWindow::~FlutterWindow() {}
bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) {
return false;
}
RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false;
}
RegisterPlugins(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
return true;
}
void FlutterWindow::OnDestroy() {
if (flutter_controller_) {
flutter_controller_ = nullptr;
}
Win32Window::OnDestroy();
}
LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
// Give Flutter, including plugins, an opportunity to handle window messages.
if (flutter_controller_) {
std::optional<LRESULT> result =
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
lparam);
if (result) {
return *result;
}
}
switch (message) {
case WM_FONTCHANGE:
flutter_controller_->engine()->ReloadSystemFonts();
break;
}
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}

View File

@@ -0,0 +1,33 @@
#ifndef RUNNER_FLUTTER_WINDOW_H_
#define RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <memory>
#include "win32_window.h"
// A window that does nothing but host a Flutter view.
class FlutterWindow : public Win32Window {
public:
// Creates a new FlutterWindow hosting a Flutter view running |project|.
explicit FlutterWindow(const flutter::DartProject& project);
virtual ~FlutterWindow();
protected:
// Win32Window:
bool OnCreate() override;
void OnDestroy() override;
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
LPARAM const lparam) noexcept override;
private:
// The project to run.
flutter::DartProject project_;
// The Flutter instance hosted by this window.
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
};
#endif // RUNNER_FLUTTER_WINDOW_H_

43
windows/runner/main.cpp Normal file
View File

@@ -0,0 +1,43 @@
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include "flutter_window.h"
#include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) {
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
CreateAndAttachConsole();
}
// Initialize COM, so that it is available for use in the library and/or
// plugins.
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
flutter::DartProject project(L"data");
std::vector<std::string> command_line_arguments =
GetCommandLineArguments();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"tasktracker", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);
::MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
::CoUninitialize();
return EXIT_SUCCESS;
}

16
windows/runner/resource.h Normal file
View File

@@ -0,0 +1,16 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Runner.rc
//
#define IDI_APP_ICON 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
</assembly>

64
windows/runner/utils.cpp Normal file
View File

@@ -0,0 +1,64 @@
#include "utils.h"
#include <flutter_windows.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
void CreateAndAttachConsole() {
if (::AllocConsole()) {
FILE *unused;
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
_dup2(_fileno(stdout), 1);
}
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
_dup2(_fileno(stdout), 2);
}
std::ios::sync_with_stdio();
FlutterDesktopResyncOutputStreams();
}
}
std::vector<std::string> GetCommandLineArguments() {
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
int argc;
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
if (argv == nullptr) {
return std::vector<std::string>();
}
std::vector<std::string> command_line_arguments;
// Skip the first argument as it's the binary name.
for (int i = 1; i < argc; i++) {
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
}
::LocalFree(argv);
return command_line_arguments;
}
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
if (utf16_string == nullptr) {
return std::string();
}
int target_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, nullptr, 0, nullptr, nullptr);
if (target_length == 0) {
return std::string();
}
std::string utf8_string;
utf8_string.resize(target_length);
int converted_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, utf8_string.data(),
target_length, nullptr, nullptr);
if (converted_length == 0) {
return std::string();
}
return utf8_string;
}

19
windows/runner/utils.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef RUNNER_UTILS_H_
#define RUNNER_UTILS_H_
#include <string>
#include <vector>
// Creates a console for the process, and redirects stdout and stderr to
// it for both the runner and the Flutter library.
void CreateAndAttachConsole();
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
// encoded in UTF-8. Returns an empty std::string on failure.
std::string Utf8FromUtf16(const wchar_t* utf16_string);
// Gets the command line arguments passed in as a std::vector<std::string>,
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
std::vector<std::string> GetCommandLineArguments();
#endif // RUNNER_UTILS_H_

View File

@@ -0,0 +1,245 @@
#include "win32_window.h"
#include <flutter_windows.h>
#include "resource.h"
namespace {
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
// The number of Win32Window objects that currently exist.
static int g_active_window_count = 0;
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
// Scale helper to convert logical scaler values to physical using passed in
// scale factor
int Scale(int source, double scale_factor) {
return static_cast<int>(source * scale_factor);
}
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
// This API is only needed for PerMonitor V1 awareness mode.
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
HMODULE user32_module = LoadLibraryA("User32.dll");
if (!user32_module) {
return;
}
auto enable_non_client_dpi_scaling =
reinterpret_cast<EnableNonClientDpiScaling*>(
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
if (enable_non_client_dpi_scaling != nullptr) {
enable_non_client_dpi_scaling(hwnd);
FreeLibrary(user32_module);
}
}
} // namespace
// Manages the Win32Window's window class registration.
class WindowClassRegistrar {
public:
~WindowClassRegistrar() = default;
// Returns the singleton registar instance.
static WindowClassRegistrar* GetInstance() {
if (!instance_) {
instance_ = new WindowClassRegistrar();
}
return instance_;
}
// Returns the name of the window class, registering the class if it hasn't
// previously been registered.
const wchar_t* GetWindowClass();
// Unregisters the window class. Should only be called if there are no
// instances of the window.
void UnregisterWindowClass();
private:
WindowClassRegistrar() = default;
static WindowClassRegistrar* instance_;
bool class_registered_ = false;
};
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
const wchar_t* WindowClassRegistrar::GetWindowClass() {
if (!class_registered_) {
WNDCLASS window_class{};
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.lpszClassName = kWindowClassName;
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = GetModuleHandle(nullptr);
window_class.hIcon =
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
window_class.hbrBackground = 0;
window_class.lpszMenuName = nullptr;
window_class.lpfnWndProc = Win32Window::WndProc;
RegisterClass(&window_class);
class_registered_ = true;
}
return kWindowClassName;
}
void WindowClassRegistrar::UnregisterWindowClass() {
UnregisterClass(kWindowClassName, nullptr);
class_registered_ = false;
}
Win32Window::Win32Window() {
++g_active_window_count;
}
Win32Window::~Win32Window() {
--g_active_window_count;
Destroy();
}
bool Win32Window::CreateAndShow(const std::wstring& title,
const Point& origin,
const Size& size) {
Destroy();
const wchar_t* window_class =
WindowClassRegistrar::GetInstance()->GetWindowClass();
const POINT target_point = {static_cast<LONG>(origin.x),
static_cast<LONG>(origin.y)};
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
double scale_factor = dpi / 96.0;
HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this);
if (!window) {
return false;
}
return OnCreate();
}
// static
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
if (message == WM_NCCREATE) {
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
SetWindowLongPtr(window, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
EnableFullDpiSupportIfAvailable(window);
that->window_handle_ = window;
} else if (Win32Window* that = GetThisFromHandle(window)) {
return that->MessageHandler(window, message, wparam, lparam);
}
return DefWindowProc(window, message, wparam, lparam);
}
LRESULT
Win32Window::MessageHandler(HWND hwnd,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
switch (message) {
case WM_DESTROY:
window_handle_ = nullptr;
Destroy();
if (quit_on_close_) {
PostQuitMessage(0);
}
return 0;
case WM_DPICHANGED: {
auto newRectSize = reinterpret_cast<RECT*>(lparam);
LONG newWidth = newRectSize->right - newRectSize->left;
LONG newHeight = newRectSize->bottom - newRectSize->top;
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
case WM_SIZE: {
RECT rect = GetClientArea();
if (child_content_ != nullptr) {
// Size and position the child window.
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, TRUE);
}
return 0;
}
case WM_ACTIVATE:
if (child_content_ != nullptr) {
SetFocus(child_content_);
}
return 0;
}
return DefWindowProc(window_handle_, message, wparam, lparam);
}
void Win32Window::Destroy() {
OnDestroy();
if (window_handle_) {
DestroyWindow(window_handle_);
window_handle_ = nullptr;
}
if (g_active_window_count == 0) {
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
}
}
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
return reinterpret_cast<Win32Window*>(
GetWindowLongPtr(window, GWLP_USERDATA));
}
void Win32Window::SetChildContent(HWND content) {
child_content_ = content;
SetParent(content, window_handle_);
RECT frame = GetClientArea();
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true);
SetFocus(child_content_);
}
RECT Win32Window::GetClientArea() {
RECT frame;
GetClientRect(window_handle_, &frame);
return frame;
}
HWND Win32Window::GetHandle() {
return window_handle_;
}
void Win32Window::SetQuitOnClose(bool quit_on_close) {
quit_on_close_ = quit_on_close;
}
bool Win32Window::OnCreate() {
// No-op; provided for subclasses.
return true;
}
void Win32Window::OnDestroy() {
// No-op; provided for subclasses.
}

View File

@@ -0,0 +1,98 @@
#ifndef RUNNER_WIN32_WINDOW_H_
#define RUNNER_WIN32_WINDOW_H_
#include <windows.h>
#include <functional>
#include <memory>
#include <string>
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
// inherited from by classes that wish to specialize with custom
// rendering and input handling
class Win32Window {
public:
struct Point {
unsigned int x;
unsigned int y;
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
};
struct Size {
unsigned int width;
unsigned int height;
Size(unsigned int width, unsigned int height)
: width(width), height(height) {}
};
Win32Window();
virtual ~Win32Window();
// Creates and shows a win32 window with |title| and position and size using
// |origin| and |size|. New windows are created on the default monitor. Window
// sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size to will treat the width height passed in to this function
// as logical pixels and scale to appropriate for the default monitor. Returns
// true if the window was created successfully.
bool CreateAndShow(const std::wstring& title,
const Point& origin,
const Size& size);
// Release OS resources associated with window.
void Destroy();
// Inserts |content| into the window tree.
void SetChildContent(HWND content);
// Returns the backing Window handle to enable clients to set icon and other
// window properties. Returns nullptr if the window has been destroyed.
HWND GetHandle();
// If true, closing this window will quit the application.
void SetQuitOnClose(bool quit_on_close);
// Return a RECT representing the bounds of the current client area.
RECT GetClientArea();
protected:
// Processes and route salient window messages for mouse handling,
// size change and DPI. Delegates handling of these to member overloads that
// inheriting classes can handle.
virtual LRESULT MessageHandler(HWND window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Called when CreateAndShow is called, allowing subclass window-related
// setup. Subclasses should return false if setup fails.
virtual bool OnCreate();
// Called when Destroy is called.
virtual void OnDestroy();
private:
friend class WindowClassRegistrar;
// OS callback called by message pump. Handles the WM_NCCREATE message which
// is passed when the non-client area is being created and enables automatic
// non-client DPI scaling so that the non-client area automatically
// responsponds to changes in DPI. All other messages are handled by
// MessageHandler.
static LRESULT CALLBACK WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Retrieves a class instance pointer for |window|
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
bool quit_on_close_ = false;
// window handle for top level window.
HWND window_handle_ = nullptr;
// window handle for hosted content.
HWND child_content_ = nullptr;
};
#endif // RUNNER_WIN32_WINDOW_H_