diff --git a/.metadata b/.metadata index 13e63571..cbf1dc0e 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" + revision: "a14f74ff3a1cbd521163c5f03d68113d50af93d3" channel: "stable" project_type: app @@ -13,11 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 - base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + - platform: android + create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + - platform: ios + create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + - platform: linux + create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - platform: macos - create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 - base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + - platform: web + create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + - platform: windows + create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 # User provided section diff --git a/android/app/build.gradle b/android/app/build.gradle index b6f87469..6edc1d75 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -52,7 +52,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.ccextractor.taskwarriorflutter" //minSdkVersion flutter.minSdkVersion - minSdkVersion 19 + minSdkVersion flutter.minSdkVersion targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/android/app/src/main/kotlin/com/ccextractor/taskwarrior/MainActivity.kt b/android/app/src/main/kotlin/com/ccextractor/taskwarrior/MainActivity.kt new file mode 100644 index 00000000..d2e8d9ba --- /dev/null +++ b/android/app/src/main/kotlin/com/ccextractor/taskwarrior/MainActivity.kt @@ -0,0 +1,5 @@ +package com.ccextractor.taskwarrior + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/assets/fonts/OFL.txt b/assets/fonts/OFL.txt new file mode 100644 index 00000000..76df3b56 --- /dev/null +++ b/assets/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/fonts/Poppins-Bold.ttf b/assets/fonts/Poppins-Bold.ttf new file mode 100644 index 00000000..b94d47f3 Binary files /dev/null and b/assets/fonts/Poppins-Bold.ttf differ diff --git a/assets/fonts/Poppins-ExtraBold.ttf b/assets/fonts/Poppins-ExtraBold.ttf new file mode 100644 index 00000000..8f008c36 Binary files /dev/null and b/assets/fonts/Poppins-ExtraBold.ttf differ diff --git a/assets/fonts/Poppins-Regular.ttf b/assets/fonts/Poppins-Regular.ttf new file mode 100644 index 00000000..be06e7fd Binary files /dev/null and b/assets/fonts/Poppins-Regular.ttf differ diff --git a/assets/fonts/SFProDisplay-Regular.ttf b/assets/fonts/SFProDisplay-Regular.ttf new file mode 100644 index 00000000..5ae2fafb Binary files /dev/null and b/assets/fonts/SFProDisplay-Regular.ttf differ diff --git a/assets/fonts/SegoeUI.ttf b/assets/fonts/SegoeUI.ttf new file mode 100644 index 00000000..46b3b993 Binary files /dev/null and b/assets/fonts/SegoeUI.ttf differ diff --git a/assets/fonts/Ubuntu-Light.ttf b/assets/fonts/Ubuntu-Light.ttf new file mode 100644 index 00000000..0e9f90d7 Binary files /dev/null and b/assets/fonts/Ubuntu-Light.ttf differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9fa42e21..a7b8abbb 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -54,7 +54,7 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - permission_handler_apple (9.1.1): + - permission_handler_apple (9.0.4): - Flutter - ReachabilitySwift (5.0.0) - SDWebImage (5.19.0): @@ -134,8 +134,8 @@ SPEC CHECKSUMS: flutter_native_timezone: 5f05b2de06c9776b4cc70e1839f03de178394d22 home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 - path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 - permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 SDWebImage: 981fd7e860af070920f249fd092420006014c3eb shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 @@ -144,4 +144,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 -COCOAPODS: 1.15.2 +COCOAPODS: 1.14.3 diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/model/chart.dart b/lib/app/models/chart.dart similarity index 100% rename from lib/model/chart.dart rename to lib/app/models/chart.dart diff --git a/lib/model/data.dart b/lib/app/models/data.dart similarity index 96% rename from lib/model/data.dart rename to lib/app/models/data.dart index 3703ffa6..3fe5c66c 100644 --- a/lib/model/data.dart +++ b/lib/app/models/data.dart @@ -4,11 +4,10 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:io'; -import 'package:taskwarrior/model/json/task.dart'; -import 'package:taskwarrior/services/notification_services.dart'; -import 'package:taskwarrior/widgets/taskc/payload.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; - +import 'package:taskwarrior/app/models/json/task.dart'; +import 'package:taskwarrior/app/services/notification_services.dart'; +import 'package:taskwarrior/app/utils/taskc/payload.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/urgency.dart'; class Data { Data(this.home); diff --git a/lib/app/models/filters.dart b/lib/app/models/filters.dart new file mode 100644 index 00000000..a2296246 --- /dev/null +++ b/lib/app/models/filters.dart @@ -0,0 +1,23 @@ +import 'package:taskwarrior/app/services/tag_filter.dart'; + +class Filters { + const Filters({ + required this.pendingFilter, + required this.waitingFilter, + required this.togglePendingFilter, + required this.toggleWaitingFilter, + required this.tagFilters, + required this.projects, + required this.projectFilter, + required this.toggleProjectFilter, + }); + + final bool pendingFilter; + final bool waitingFilter; + final void Function() togglePendingFilter; + final void Function() toggleWaitingFilter; + final TagFilters tagFilters; + final dynamic projects; + final String projectFilter; + final void Function(String) toggleProjectFilter; +} \ No newline at end of file diff --git a/lib/model/json/annotation.dart b/lib/app/models/json/annotation.dart similarity index 91% rename from lib/model/json/annotation.dart rename to lib/app/models/json/annotation.dart index f0d6f138..5fe2b34a 100644 --- a/lib/model/json/annotation.dart +++ b/lib/app/models/json/annotation.dart @@ -2,8 +2,9 @@ import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; +import 'package:taskwarrior/app/models/json/serializers.dart'; + -import 'package:taskwarrior/model/json.dart'; part 'annotation.g.dart'; diff --git a/lib/model/json/annotation.g.dart b/lib/app/models/json/annotation.g.dart similarity index 100% rename from lib/model/json/annotation.g.dart rename to lib/app/models/json/annotation.g.dart diff --git a/lib/model/json/iso_8601_basic.dart b/lib/app/models/json/iso_8601_basic.dart similarity index 100% rename from lib/model/json/iso_8601_basic.dart rename to lib/app/models/json/iso_8601_basic.dart diff --git a/lib/model/json/serializers.dart b/lib/app/models/json/serializers.dart similarity index 70% rename from lib/model/json/serializers.dart rename to lib/app/models/json/serializers.dart index f2abc13f..fce4ca6b 100644 --- a/lib/model/json/serializers.dart +++ b/lib/app/models/json/serializers.dart @@ -3,8 +3,11 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/serializer.dart'; import 'package:built_value/standard_json_plugin.dart'; +import 'package:taskwarrior/app/models/json/annotation.dart'; +import 'package:taskwarrior/app/models/json/iso_8601_basic.dart'; +import 'package:taskwarrior/app/models/json/task.dart'; + -import 'package:taskwarrior/model/json.dart'; part 'serializers.g.dart'; diff --git a/lib/model/json/serializers.g.dart b/lib/app/models/json/serializers.g.dart similarity index 100% rename from lib/model/json/serializers.g.dart rename to lib/app/models/json/serializers.g.dart diff --git a/lib/model/json/task.dart b/lib/app/models/json/task.dart similarity index 92% rename from lib/model/json/task.dart rename to lib/app/models/json/task.dart index d984c9d4..b178ef35 100644 --- a/lib/model/json/task.dart +++ b/lib/app/models/json/task.dart @@ -5,9 +5,10 @@ import 'dart:convert'; import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; +import 'package:taskwarrior/app/models/json/annotation.dart'; +import 'package:taskwarrior/app/models/json/serializers.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/validate.dart'; -import 'package:taskwarrior/model/json.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; part 'task.g.dart'; diff --git a/lib/model/json/task.g.dart b/lib/app/models/json/task.g.dart similarity index 100% rename from lib/model/json/task.g.dart rename to lib/app/models/json/task.g.dart diff --git a/lib/app/models/models.dart b/lib/app/models/models.dart new file mode 100644 index 00000000..6eabc8b9 --- /dev/null +++ b/lib/app/models/models.dart @@ -0,0 +1,6 @@ +export './onboarding_model.dart'; +export './size_config_model.dart'; +export './json/annotation.dart'; +export './json/iso_8601_basic.dart'; +export './json/serializers.dart'; +export './json/task.dart'; \ No newline at end of file diff --git a/lib/app/models/onboarding_model.dart b/lib/app/models/onboarding_model.dart new file mode 100644 index 00000000..ab0796a2 --- /dev/null +++ b/lib/app/models/onboarding_model.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class OnboardingModel { + final String title; + final String image; + final String desc; + final Color colors; + + OnboardingModel({ + required this.title, + required this.image, + required this.desc, + required this.colors, + }); +} \ No newline at end of file diff --git a/lib/views/Onboarding/Components/size_config.dart b/lib/app/models/size_config_model.dart similarity index 67% rename from lib/views/Onboarding/Components/size_config.dart rename to lib/app/models/size_config_model.dart index 8563db5d..00271a17 100644 --- a/lib/views/Onboarding/Components/size_config.dart +++ b/lib/app/models/size_config_model.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; class SizeConfig { - static MediaQueryData? _mediaQueryData; - static double? screenW; - static double? screenH; - static double? blockH; - static double? blockV; + MediaQueryData? _mediaQueryData; + double? screenW; + double? screenH; + double? blockH; + double? blockV; void init(BuildContext context) { _mediaQueryData = MediaQuery.of(context); diff --git a/lib/model/storage.dart b/lib/app/models/storage.dart similarity index 51% rename from lib/model/storage.dart rename to lib/app/models/storage.dart index 0679912d..78212b0d 100644 --- a/lib/model/storage.dart +++ b/lib/app/models/storage.dart @@ -1,11 +1,12 @@ import 'dart:io'; -import 'package:taskwarrior/model/data.dart'; -import 'package:taskwarrior/model/storage/tabs.dart'; -import 'package:taskwarrior/widgets/home_paths/home.dart'; -import 'package:taskwarrior/widgets/home_paths/impl/gui_pem_file_paths.dart'; -import 'package:taskwarrior/widgets/home_paths/impl/taskrc.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; +import 'package:taskwarrior/app/models/data.dart'; +import 'package:taskwarrior/app/models/storage/tabs.dart'; +import 'package:taskwarrior/app/utils/home_path/impl/gui_pem_file_paths.dart'; +import 'package:taskwarrior/app/utils/home_path/impl/home.dart'; +import 'package:taskwarrior/app/utils/home_path/impl/taskrc.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/query.dart'; + class Storage { const Storage(this.profile); diff --git a/lib/model/storage/client.dart b/lib/app/models/storage/client.dart similarity index 100% rename from lib/model/storage/client.dart rename to lib/app/models/storage/client.dart diff --git a/lib/model/storage/exceptions/bad_certificate_exception.dart b/lib/app/models/storage/exceptions/bad_certificate_exception.dart similarity index 100% rename from lib/model/storage/exceptions/bad_certificate_exception.dart rename to lib/app/models/storage/exceptions/bad_certificate_exception.dart diff --git a/lib/model/storage/exceptions/taskserver_configuration_exception.dart b/lib/app/models/storage/exceptions/taskserver_configuration_exception.dart similarity index 100% rename from lib/model/storage/exceptions/taskserver_configuration_exception.dart rename to lib/app/models/storage/exceptions/taskserver_configuration_exception.dart diff --git a/lib/model/storage/savefile.dart b/lib/app/models/storage/savefile.dart similarity index 100% rename from lib/model/storage/savefile.dart rename to lib/app/models/storage/savefile.dart diff --git a/lib/model/storage/set_config.dart b/lib/app/models/storage/set_config.dart similarity index 94% rename from lib/model/storage/set_config.dart rename to lib/app/models/storage/set_config.dart index 908e8580..61962e9c 100644 --- a/lib/model/storage/set_config.dart +++ b/lib/app/models/storage/set_config.dart @@ -2,8 +2,8 @@ import 'dart:io'; import 'package:file_picker_writable/file_picker_writable.dart'; import 'package:file_selector/file_selector.dart'; +import 'package:taskwarrior/app/models/storage.dart'; -import 'package:taskwarrior/model/storage.dart'; Future setConfig({required Storage storage, required String key}) async { String? contents; diff --git a/lib/model/storage/tabs.dart b/lib/app/models/storage/tabs.dart similarity index 100% rename from lib/model/storage/tabs.dart rename to lib/app/models/storage/tabs.dart diff --git a/lib/app/models/tag_meta_data.dart b/lib/app/models/tag_meta_data.dart new file mode 100644 index 00000000..6e2bdfb9 --- /dev/null +++ b/lib/app/models/tag_meta_data.dart @@ -0,0 +1,20 @@ +class TagMetadata { + TagMetadata({ + required this.lastModified, + required this.frequency, + required this.selected, + }); + + final DateTime lastModified; + final int frequency; + final bool selected; + + Map toJson() => { + 'lastModified': lastModified, + 'frequency': frequency, + 'selected': selected, + }; + + @override + String toString() => toJson().toString(); +} diff --git a/lib/app/modules/about/bindings/about_binding.dart b/lib/app/modules/about/bindings/about_binding.dart new file mode 100644 index 00000000..388e1fea --- /dev/null +++ b/lib/app/modules/about/bindings/about_binding.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; + +import '../controllers/about_controller.dart'; + +class AboutBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => AboutController(), + ); + } +} diff --git a/lib/app/modules/about/controllers/about_controller.dart b/lib/app/modules/about/controllers/about_controller.dart new file mode 100644 index 00000000..3e35f687 --- /dev/null +++ b/lib/app/modules/about/controllers/about_controller.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; + +class AboutController extends GetxController { + //TODO: Implement AboutController + + final count = 0.obs; + + + + void increment() => count.value++; +} diff --git a/lib/app/modules/about/views/about_page_app_bar.dart b/lib/app/modules/about/views/about_page_app_bar.dart new file mode 100644 index 00000000..390fa2e4 --- /dev/null +++ b/lib/app/modules/about/views/about_page_app_bar.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + + +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; + +class AboutPageAppBar extends StatelessWidget implements PreferredSizeWidget { + const AboutPageAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return AppBar( + centerTitle: true, + backgroundColor: TaskWarriorColors.kprimaryBackgroundColor, + title: Text( + 'About', + // style: GoogleFonts.poppins(color: TaskWarriorColors.white), + style: TextStyle( + fontFamily: FontFamily.poppins, + color: TaskWarriorColors.white, + ), + ), + leading: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Icon( + Icons.chevron_left, + color: TaskWarriorColors.white, + ), + ), + ); + } + + @override + Size get preferredSize => AppBar().preferredSize; +} diff --git a/lib/app/modules/about/views/about_page_body.dart b/lib/app/modules/about/views/about_page_body.dart new file mode 100644 index 00000000..e6e42368 --- /dev/null +++ b/lib/app/modules/about/views/about_page_body.dart @@ -0,0 +1,275 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import 'package:get/get.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:taskwarrior/app/utils/gen/assets.gen.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class AboutPageBody extends StatelessWidget { + const AboutPageBody({super.key}); + + @override + Widget build(BuildContext context) { + String introduction = + "This project aims to build an app for Taskwarrior. It is your task management app across all platforms. It helps you manage your tasks and filter them as per your needs."; + + return Padding( + padding: EdgeInsets.only( + top: Get.height * 0.01, + left: Get.width * 0.02, + right: Get.width * 0.02), + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + height: Get.height * 0.02, + ), + SizedBox( + child: SvgPicture.asset( + Assets.svg.logo.path, + height: Get.height * 0.2, + width: Get.width * 1, + )), + SizedBox( + height: Get.height * 0.02, + ), + Text( + "Taskwarrior", + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeExtraLarge, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + SizedBox( + height: Get.height * 0.02, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FutureBuilder( + future: getAppInfo(), + builder: + (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const CircularProgressIndicator(); + } else if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } else { + final appInfoLines = snapshot.data!.split(' '); + + return Column( + children: [ + RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'Version: ', + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + TextSpan( + text: appInfoLines[1], + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ), + SizedBox( + width: Get.width * 0.85, + child: FittedBox( + fit: BoxFit.fitWidth, + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'Package: ', + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + fontSize: + TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + TextSpan( + text: appInfoLines[0], + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: + TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ), + ), + ), + ], + ); + } + }, + ), + ], + ), + SizedBox( + height: Get.height * 0.05, + ), + Text( + introduction, + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.medium, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + SizedBox( + height: Get.height * 0.06, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + SizedBox( + width: Get.width * 0.4, + height: Get.height * 0.05, + child: ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kLightSecondaryBackgroundColor + : TaskWarriorColors.ksecondaryBackgroundColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + onPressed: () async { + // Launch GitHub URL. + + String url = + "https://github.com/CCExtractor/taskwarrior-flutter"; + if (!await launchUrl(Uri.parse(url))) { + throw Exception('Could not launch $url'); + } + }, + icon: SvgPicture.asset(Assets.svg.github.path, + width: 20, + height: 20, + colorFilter: ColorFilter.mode( + AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + BlendMode.srcIn)), + label: Text( + "GitHub", + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.medium, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + ), + ), + ), + ), + SizedBox( + width: Get.width * 0.4, + height: Get.height * 0.05, + child: ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kLightSecondaryBackgroundColor + : TaskWarriorColors.ksecondaryBackgroundColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + onPressed: () async { + String url = "https://ccextractor.org/"; + if (!await launchUrl(Uri.parse(url))) { + throw Exception('Could not launch $url'); + } + }, + icon: SvgPicture.asset("assets/svg/link.svg", + width: 20, + height: 20, + colorFilter: ColorFilter.mode( + AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + BlendMode.srcIn)), + label: Text( + "CCExtractor", + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.medium, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + ), + ), + ), + ), + ], + ), + SizedBox( + height: Get.height * 0.04, + ), + Text( + "Eager to enhance this project? Visit our GitHub repository.", + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.semiBold, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + SizedBox( + height: Get.height * 0.02, + ), + ], + ), + ), + ); + } +} + +Future getAppInfo() async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + + return '${packageInfo.packageName} ${packageInfo.version}'; +} diff --git a/lib/app/modules/about/views/about_view.dart b/lib/app/modules/about/views/about_view.dart new file mode 100644 index 00000000..b1203a44 --- /dev/null +++ b/lib/app/modules/about/views/about_view.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; + +import 'package:taskwarrior/app/modules/about/views/about_page_app_bar.dart'; +import 'package:taskwarrior/app/modules/about/views/about_page_body.dart'; + +import '../controllers/about_controller.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class AboutView extends GetView { + const AboutView({super.key}); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const AboutPageAppBar(), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.white, + body: const AboutPageBody(), + ); + } +} diff --git a/lib/app/modules/detailRoute/bindings/detail_route_binding.dart b/lib/app/modules/detailRoute/bindings/detail_route_binding.dart new file mode 100644 index 00000000..41eebd1e --- /dev/null +++ b/lib/app/modules/detailRoute/bindings/detail_route_binding.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; + +import '../controllers/detail_route_controller.dart'; + +class DetailRouteBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => DetailRouteController(), + ); + } +} diff --git a/lib/app/modules/detailRoute/controllers/detail_route_controller.dart b/lib/app/modules/detailRoute/controllers/detail_route_controller.dart new file mode 100644 index 00000000..eb26f4ca --- /dev/null +++ b/lib/app/modules/detailRoute/controllers/detail_route_controller.dart @@ -0,0 +1,88 @@ +import 'package:built_collection/built_collection.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/modify.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/urgency.dart'; + +class DetailRouteController extends GetxController { + late String uuid; + late Modify modify; + var onEdit = false.obs; + + @override + void onInit() { + super.onInit(); + var arguments = Get.arguments; + uuid = arguments[1] as String; + // uuid = Get.arguments['uuid']; + var storageWidget = Get.find(); + modify = Modify( + getTask: storageWidget.getTask, + mergeTask: storageWidget.mergeTask, + uuid: uuid, + ); + initValues(); + } + + void setAttribute(String name, dynamic newValue) { + modify.set(name, newValue); + onEdit.value = true; + initValues(); + } + + Future saveChanges() async { + var now = DateTime.now().toUtc(); + modify.save(modified: () => now); + onEdit.value = false; + Get.back(); + Get.snackbar( + 'Task Updated', + '', + snackPosition: SnackPosition.BOTTOM, + ); + } + + // 'description': controller.modify.draft.description, + // 'status': controller.modify.draft.status, + // 'entry': controller.modify.draft.entry, + // 'modified': controller.modify.draft.modified, + // 'start': controller.modify.draft.start, + // 'end': controller.modify.draft.end, + // 'due': controller.dueValue.value, + // 'wait': controller.modify.draft.wait, + // 'until': controller.modify.draft.until, + // 'priority': controller.modify.draft.priority, + // 'project': controller.modify.draft.project, + // 'tags': controller.modify.draft.tags, + // 'urgency': urgency(controller.modify.draft + + late RxString descriptionValue = ''.obs; + late RxString statusValue = ''.obs; + late Rx entryValue = Rx(null); + late Rx modifiedValue = Rx(null); + late Rx startValue = Rx(null); + late Rx endValue = Rx(null); + late Rx dueValue = Rx(null); + late Rx waitValue = Rx(null); + late Rx untilValue = Rx(null); + late Rxn? priorityValue = Rxn(null); + late Rxn? projectValue = Rxn(null); + late Rxn>? tagsValue = Rxn>(null); + late RxDouble urgencyValue = 0.0.obs; + + void initValues() { + descriptionValue.value = modify.draft.description; + statusValue.value = modify.draft.status; + entryValue.value = modify.draft.entry; + modifiedValue.value = modify.draft.modified; + startValue.value = modify.draft.start; + endValue.value = modify.draft.end; + dueValue.value = modify.draft.due; + waitValue.value = modify.draft.wait; + untilValue.value = modify.draft.until; + priorityValue?.value = modify.draft.priority; + projectValue?.value = modify.draft.project; + tagsValue?.value = modify.draft.tags; + urgencyValue.value = urgency(modify.draft); + } +} diff --git a/lib/app/modules/detailRoute/views/dateTimePicker.dart b/lib/app/modules/detailRoute/views/dateTimePicker.dart new file mode 100644 index 00000000..b2c40f66 --- /dev/null +++ b/lib/app/modules/detailRoute/views/dateTimePicker.dart @@ -0,0 +1,281 @@ +// ignore_for_file: file_names, use_build_context_synchronously + +import 'package:flutter/material.dart'; + +import 'package:intl/intl.dart'; + +import 'package:taskwarrior/app/utils/constants/constants.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class DateTimeWidget extends StatelessWidget { + const DateTimeWidget({ + super.key, + required this.name, + required this.value, + required this.callback, + }); + + final String name; + + final dynamic value; + final void Function(dynamic) callback; + + @override + Widget build(BuildContext context) { + return Card( + color: AppSettings.isDarkMode + ? const Color.fromARGB(255, 57, 57, 57) + : Colors.white, + child: ListTile( + textColor: AppSettings.isDarkMode + ? Colors.white + : const Color.fromARGB(255, 48, 46, 46), + title: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + RichText( + text: TextSpan( + children: [ + TextSpan( + text: '$name:'.padRight(13), + // style: GoogleFonts.poppins( + // fontWeight: TaskWarriorFonts.bold, + // fontSize: TaskWarriorFonts.fontSizeMedium, + // color: AppSettings.isDarkMode + // ? Colors.white + // : Colors.black, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + TextSpan( + text: value ?? "not selected", + // style: GoogleFonts.poppins( + // fontSize: TaskWarriorFonts.fontSizeMedium, + // color: AppSettings.isDarkMode + // ? Colors.white + // : Colors.black, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ), + ], + ), + ), + // onTap: () async { + // var initialDate = DateFormat("E, M/d/y h:mm:ss a").parse( + // value?.replaceAll(RegExp(r'\s+'), ' ') ?? + // DateFormat("E, M/d/y h:mm:ss a").format(DateTime.now())); + + // var date = await showDatePicker( + // context: context, + // initialDate: initialDate, + // firstDate: DateTime + // .now(), // sets the earliest selectable date to the current date. This prevents the user from selecting a date in the past. + // lastDate: DateTime(2037, 12, 31), // < 2038-01-19T03:14:08.000Z + // ); + // if (date != null) { + // var time = await showTimePicker( + // context: context, + // initialTime: TimeOfDay.now(), + // ); + // if (time != null) { + // var dateTime = date.add( + // Duration( + // hours: time.hour, + // minutes: time.minute, + // ), + // ); + // dateTime = dateTime.add( + // Duration( + // hours: time.hour - dateTime.hour, + // ), + // ); + // // Check if the selected time is in the past + // if (dateTime.isBefore(DateTime.now())) { + // // Show a message that past times can't be set + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text( + // "Can't set times in the past", + // style: TextStyle( + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.kprimaryTextColor + // : TaskWarriorColors.kLightPrimaryTextColor, + // ), + // ), + // backgroundColor: AppSettings.isDarkMode + // ? TaskWarriorColors.ksecondaryBackgroundColor + // : TaskWarriorColors.kLightSecondaryBackgroundColor, + // duration: const Duration(seconds: 2), + // ), + // ); + // } else { + // // If the time is not in the past, proceed as usual + // return callback(dateTime.toUtc()); + // } + // } + // } + // }, + onTap: () async { + var parsedDate = DateFormat("E, M/d/y h:mm:ss a").parse( + value?.replaceAll(RegExp(r'\s+'), ' ') ?? + DateFormat("E, M/d/y h:mm:ss a").format(DateTime.now())); + + var now = DateTime.now(); + var initialDate = parsedDate.isBefore(now) ? now : parsedDate; + + var date = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: now, + lastDate: DateTime(2037, 12, 31), // < 2038-01-19T03:14:08.000Z + ); + + if (date != null) { + var time = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ); + if (time != null) { + var dateTime = date.add( + Duration( + hours: time.hour, + minutes: time.minute, + ), + ); + + if (dateTime.isBefore(DateTime.now())) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Can't set times in the past", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2), + ), + ); + } else { + return callback(dateTime.toUtc()); + } + } + } + }, + + onLongPress: () => callback(null), + ), + ); + } +} + +class StartWidget extends StatelessWidget { + const StartWidget({ + required this.name, + required this.value, + required this.callback, + super.key, + }); + + final String name; + final dynamic value; + final void Function(dynamic) callback; + + @override + Widget build(BuildContext context) { + return Card( + color: AppSettings.isDarkMode + ? const Color.fromARGB(255, 57, 57, 57) + : Colors.white, + child: ListTile( + textColor: AppSettings.isDarkMode + ? Colors.white + : const Color.fromARGB(255, 48, 46, 46), + title: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + RichText( + text: TextSpan( + children: [ + TextSpan( + text: '$name:'.padRight(13), + // style: GoogleFonts.poppins( + // fontWeight: TaskWarriorFonts.bold, + // fontSize: TaskWarriorFonts.fontSizeMedium, + // color: AppSettings.isDarkMode + // ? Colors.white + // : Colors.black, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + TextSpan( + text: value ?? "not selected", + // style: GoogleFonts.poppins( + // fontSize: TaskWarriorFonts.fontSizeMedium, + // color: AppSettings.isDarkMode + // ? Colors.white + // : Colors.black, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ), + ], + ), + ), + onTap: () { + if (value != null) { + callback(null); + } else { + var now = DateTime.now().toUtc(); + callback(DateTime.utc( + now.year, + now.month, + now.day, + now.hour, + now.minute, + now.second, + )); + } + }, + ), + ); + } +} diff --git a/lib/widgets/taskdetails/description_widget.dart b/lib/app/modules/detailRoute/views/description_widget.dart similarity index 71% rename from lib/widgets/taskdetails/description_widget.dart rename to lib/app/modules/detailRoute/views/description_widget.dart index 260ab04c..e4f85047 100644 --- a/lib/widgets/taskdetails/description_widget.dart +++ b/lib/app/modules/detailRoute/views/description_widget.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; +import 'package:get/get.dart'; import 'package:loggy/loggy.dart'; - -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/utility/utilities.dart'; +import 'package:taskwarrior/app/utils/constants/constants.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; class DescriptionWidget extends StatelessWidget { const DescriptionWidget( @@ -38,21 +37,36 @@ class DescriptionWidget extends StatelessWidget { children: [ TextSpan( text: '$name:'.padRight(13), - style: GoogleFonts.poppins( + // style: GoogleFonts.poppins( + // fontWeight: TaskWarriorFonts.bold, + // fontSize: TaskWarriorFonts.fontSizeMedium, + // color: AppSettings.isDarkMode + // ? Colors.white + // : Colors.black, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, fontWeight: TaskWarriorFonts.bold, fontSize: TaskWarriorFonts.fontSizeMedium, color: AppSettings.isDarkMode - ? Colors.white - : Colors.black, + ? TaskWarriorColors.white + : TaskWarriorColors.black, ), ), TextSpan( text: value ?? "not selected", - style: GoogleFonts.poppins( + // style: GoogleFonts.poppins( + // fontSize: TaskWarriorFonts.fontSizeMedium, + // color: AppSettings.isDarkMode + // ? Colors.white + // : Colors.black, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, fontSize: TaskWarriorFonts.fontSizeMedium, color: AppSettings.isDarkMode - ? Colors.white - : Colors.black, + ? TaskWarriorColors.white + : TaskWarriorColors.black, ), ), ], @@ -90,7 +104,8 @@ class DescriptionWidget extends StatelessWidget { actions: [ TextButton( onPressed: () { - Navigator.of(context).pop(); + // Navigator.of(context).pop(); + Get.back(); }, child: Text( 'Cancel', @@ -105,7 +120,8 @@ class DescriptionWidget extends StatelessWidget { onPressed: () { try { callback(controller.text); - Navigator.of(context).pop(); + // Navigator.of(context).pop(); + Get.back(); } on FormatException catch (e, trace) { logError(e, trace); } @@ -158,21 +174,36 @@ class ProjectWidget extends StatelessWidget { children: [ TextSpan( text: '$name:'.padRight(13), - style: GoogleFonts.poppins( + // style: GoogleFonts.poppins( + // fontWeight: TaskWarriorFonts.bold, + // fontSize: TaskWarriorFonts.fontSizeMedium, + // color: AppSettings.isDarkMode + // ? Colors.white + // : Colors.black, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, fontWeight: TaskWarriorFonts.bold, fontSize: TaskWarriorFonts.fontSizeMedium, color: AppSettings.isDarkMode - ? Colors.white - : Colors.black, + ? TaskWarriorColors.white + : TaskWarriorColors.black, ), ), TextSpan( text: value ?? "not selected", - style: GoogleFonts.poppins( + // style: GoogleFonts.poppins( + // fontSize: TaskWarriorFonts.fontSizeMedium, + // color: AppSettings.isDarkMode + // ? Colors.white + // : Colors.black, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, fontSize: TaskWarriorFonts.fontSizeMedium, color: AppSettings.isDarkMode - ? Colors.white - : Colors.black, + ? TaskWarriorColors.white + : TaskWarriorColors.black, ), ), ], @@ -210,7 +241,8 @@ class ProjectWidget extends StatelessWidget { actions: [ TextButton( onPressed: () { - Navigator.of(context).pop(); + // Navigator.of(context).pop(); + Get.back(); }, child: Text( 'Cancel', @@ -226,7 +258,8 @@ class ProjectWidget extends StatelessWidget { try { callback( (controller.text == '') ? null : controller.text); - Navigator.of(context).pop(); + // Navigator.of(context).pop(); + Get.back(); } on FormatException catch (e, trace) { logError(e, trace); } diff --git a/lib/app/modules/detailRoute/views/detail_route_view.dart b/lib/app/modules/detailRoute/views/detail_route_view.dart new file mode 100644 index 00000000..a947f48a --- /dev/null +++ b/lib/app/modules/detailRoute/views/detail_route_view.dart @@ -0,0 +1,401 @@ +import 'package:built_collection/built_collection.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import 'package:taskwarrior/app/modules/detailRoute/controllers/detail_route_controller.dart'; +import 'package:taskwarrior/app/modules/detailRoute/views/dateTimePicker.dart'; +import 'package:taskwarrior/app/modules/detailRoute/views/description_widget.dart'; +import 'package:taskwarrior/app/modules/detailRoute/views/priority_widget.dart'; +import 'package:taskwarrior/app/modules/detailRoute/views/status_widget.dart'; +import 'package:taskwarrior/app/modules/detailRoute/views/tags_widget.dart'; +import 'package:taskwarrior/app/utils/constants/constants.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class DetailRouteView extends GetView { + const DetailRouteView({super.key}); + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + if (!controller.onEdit.value) { + // Get.offAll(() => const HomeView()); + Get.back(); + // Get.toNamed(Routes.HOME); + return false; + } + + bool? save = await showDialog( + context: context, + builder: (context) { + return AlertDialog( + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kdialogBackGroundColor + : TaskWarriorColors.kLightDialogBackGroundColor, + title: Text( + 'Do you want to save changes?', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + actions: [ + TextButton( + onPressed: () { + controller.saveChanges(); + // Get.offAll(() => const HomeView()); + + Get.back(); + }, + child: Text( + 'Yes', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ), + TextButton( + onPressed: () { + // Get.offAll(() => const HomeView()); + + Get.back(); + }, + child: Text( + 'No', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ), + TextButton( + onPressed: () { + Get.back(); + }, + child: Text( + 'Cancel', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ), + ], + ); + }, + ); + return save == true; + }, + child: Scaffold( + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + appBar: AppBar( + leading: BackButton(color: TaskWarriorColors.white), + backgroundColor: Palette.kToDark, + title: Text( + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageID}: ${(controller.modify.id == 0) ? '-' : controller.modify.id}', + style: TextStyle( + color: TaskWarriorColors.white, + ), + )), + body: Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: Obx( + () => ListView( + padding: + const EdgeInsets.symmetric(vertical: 4, horizontal: 2), + children: [ + for (var entry in { + 'description': controller.descriptionValue.value, + 'status': controller.statusValue.value, + 'entry': controller.entryValue.value, + 'modified': controller.modifiedValue.value, + 'start': controller.startValue.value, + 'end': controller.endValue.value, + 'due': controller.dueValue.value, + 'wait': controller.waitValue.value, + 'until': controller.untilValue.value, + 'priority': controller.priorityValue?.value, + 'project': controller.projectValue?.value, + 'tags': controller.tagsValue?.value, + 'urgency': controller.urgencyValue.value, + }.entries) + AttributeWidget( + name: entry.key, + value: entry.value, + callback: (newValue) => + controller.setAttribute(entry.key, newValue), + ), + ], + ), + )), + floatingActionButton: controller.modify.changes.isEmpty + ? const SizedBox.shrink() + : FloatingActionButton( + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + foregroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + splashColor: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + heroTag: "btn1", + onPressed: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + scrollable: true, + title: Text( + 'Review changes:', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + content: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Text( + controller.modify.changes.entries + .map((entry) => '${entry.key}:\n' + ' old: ${entry.value['old']}\n' + ' new: ${entry.value['new']}') + .toList() + .join('\n'), + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ), + actions: [ + TextButton( + onPressed: () { + Get.back(); + }, + child: Text( + 'Cancel', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ), + ElevatedButton( + onPressed: () { + controller.saveChanges(); + }, + child: Text( + 'Submit', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.black, + ), + ), + ), + ], + ); + }, + ); + }, + child: const Icon(Icons.save), + )), + ); + } +} + +class AttributeWidget extends StatelessWidget { + const AttributeWidget({ + required this.name, + required this.value, + required this.callback, + super.key, + }); + + final String name; + final dynamic value; + final void Function(dynamic) callback; + + @override + Widget build(BuildContext context) { + var localValue = (value is DateTime) + ? DateFormat.yMEd().add_jms().format(value.toLocal()) + : ((value is BuiltList) ? (value).toBuilder() : value); + + switch (name) { + case 'description': + return DescriptionWidget( + name: name, + value: localValue, + callback: callback, + ); + case 'status': + return StatusWidget( + name: name, + value: localValue, + callback: callback, + ); + case 'start': + return StartWidget( + name: name, + value: localValue, + callback: callback, + ); + case 'due': + return DateTimeWidget( + name: name, + value: localValue, + callback: callback, + ); + case 'wait': + return DateTimeWidget( + name: name, + value: localValue, + callback: callback, + ); + case 'until': + return DateTimeWidget( + name: name, + value: localValue, + callback: callback, + ); + case 'priority': + return PriorityWidget( + name: name, + value: localValue, + callback: callback, + ); + case 'project': + return ProjectWidget( + name: name, + value: localValue, + callback: callback, + ); + case 'tags': + return TagsWidget( + name: name, + value: localValue, + callback: callback, + ); + default: + return Card( + color: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + child: ListTile( + textColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightSecondaryTextColor, + title: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Text( + '$name:'.padRight(13), + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + Text( + localValue?.toString() ?? "not selected", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ), + ), + ); + } + } +} + +class TagsWidget extends StatelessWidget { + const TagsWidget({ + required this.name, + required this.value, + required this.callback, + super.key, + }); + + final String name; + final dynamic value; + final void Function(dynamic) callback; + @override + Widget build(BuildContext context) { + return Card( + color: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + child: ListTile( + textColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.ksecondaryTextColor, + title: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + RichText( + text: TextSpan( + children: [ + TextSpan( + text: '$name:'.padRight(13), + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ) + // style: GoogleFonts.poppins( + // fontWeight: TaskWarriorFonts.bold, + // fontSize: TaskWarriorFonts.fontSizeMedium, + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.white + // : TaskWarriorColors.black, + // ), + ), + TextSpan( + text: + '${(value as ListBuilder?)?.build() ?? 'not selected'}', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ), + ], + ), + ), + onTap: () => Get.to( + TagsRoute( + value: value, + callback: callback, + ), + ), + ), + ); + } +} diff --git a/lib/widgets/taskdetails/priority_widget.dart b/lib/app/modules/detailRoute/views/priority_widget.dart similarity index 94% rename from lib/widgets/taskdetails/priority_widget.dart rename to lib/app/modules/detailRoute/views/priority_widget.dart index 7dab467c..b3a7dc17 100644 --- a/lib/widgets/taskdetails/priority_widget.dart +++ b/lib/app/modules/detailRoute/views/priority_widget.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; - -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; +import 'package:taskwarrior/app/utils/constants/constants.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; class PriorityWidget extends StatelessWidget { const PriorityWidget( diff --git a/lib/widgets/taskdetails/status_widget.dart b/lib/app/modules/detailRoute/views/status_widget.dart similarity index 95% rename from lib/widgets/taskdetails/status_widget.dart rename to lib/app/modules/detailRoute/views/status_widget.dart index 8ceb725a..a2c2debb 100644 --- a/lib/widgets/taskdetails/status_widget.dart +++ b/lib/app/modules/detailRoute/views/status_widget.dart @@ -3,9 +3,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:taskwarrior/app/utils/constants/constants.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; class StatusWidget extends StatelessWidget { const StatusWidget( diff --git a/lib/widgets/taskdetails/tags_widget.dart b/lib/app/modules/detailRoute/views/tags_widget.dart similarity index 92% rename from lib/widgets/taskdetails/tags_widget.dart rename to lib/app/modules/detailRoute/views/tags_widget.dart index be41b3ba..4f16241a 100644 --- a/lib/widgets/taskdetails/tags_widget.dart +++ b/lib/app/modules/detailRoute/views/tags_widget.dart @@ -2,14 +2,16 @@ import 'package:flutter/material.dart'; import 'package:built_collection/built_collection.dart'; +import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:loggy/loggy.dart'; +import 'package:taskwarrior/app/models/tag_meta_data.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/utility/utilities.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; +import 'package:taskwarrior/app/utils/constants/constants.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/validate.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; class TagsWidget extends StatelessWidget { const TagsWidget({ @@ -108,7 +110,7 @@ class TagsRouteState extends State { } Future _initialize() async { - _pendingTags = StorageWidget.of(context).pendingTags; + _pendingTags = Get.find().pendingTags; setState(() {}); } @@ -228,7 +230,8 @@ class TagsRouteState extends State { actions: [ TextButton( onPressed: () { - Navigator.of(context).pop(); + // Navigator.of(context).pop(); + Get.back(); }, child: Text( 'Cancel', @@ -245,7 +248,8 @@ class TagsRouteState extends State { try { validateTaskTags(controller.text); _addTag(controller.text); - Navigator.of(context).pop(); + // Navigator.of(context).pop(); + Get.back(); } on FormatException catch (e, trace) { logError(e, trace); } diff --git a/lib/app/modules/home/bindings/home_binding.dart b/lib/app/modules/home/bindings/home_binding.dart new file mode 100644 index 00000000..4d78d594 --- /dev/null +++ b/lib/app/modules/home/bindings/home_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; + +class HomeBinding extends Bindings { + @override + void dependencies() { + Get.put( + HomeController(), + ); + } +} diff --git a/lib/app/modules/home/controllers/home_controller.dart b/lib/app/modules/home/controllers/home_controller.dart new file mode 100644 index 00000000..466feb62 --- /dev/null +++ b/lib/app/modules/home/controllers/home_controller.dart @@ -0,0 +1,629 @@ +// ignore_for_file: use_build_context_synchronously, unrelated_type_equality_checks + +import 'dart:collection'; +import 'dart:io'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:home_widget/home_widget.dart'; +import 'package:loggy/loggy.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:taskwarrior/app/models/filters.dart'; + +import 'package:taskwarrior/app/models/json/task.dart'; +import 'package:taskwarrior/app/models/storage.dart'; +import 'package:taskwarrior/app/models/storage/client.dart'; +import 'package:taskwarrior/app/models/tag_meta_data.dart'; +import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart'; +import 'package:taskwarrior/app/routes/app_pages.dart'; +import 'package:taskwarrior/app/services/tag_filter.dart'; +import 'package:taskwarrior/app/tour/filter_drawer_tour.dart'; +import 'package:taskwarrior/app/tour/home_page_tour.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/language/supported_language.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/comparator.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/projects.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/query.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/tags.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; + +class HomeController extends GetxController { + final SplashController splashController = Get.find(); + late Storage storage; + final RxBool pendingFilter = false.obs; + final RxBool waitingFilter = false.obs; + final RxString projectFilter = ''.obs; + final RxBool tagUnion = false.obs; + final RxString selectedSort = ''.obs; + final RxSet selectedTags = {}.obs; + final RxList queriedTasks = [].obs; + final RxList searchedTasks = [].obs; + final RxMap pendingTags = {}.obs; + final RxMap projects = {}.obs; + final RxBool sortHeaderVisible = false.obs; + final RxBool searchVisible = false.obs; + final TextEditingController searchController = TextEditingController(); + late RxBool serverCertExists; + final Rx selectedLanguage = SupportedLanguage.english.obs; + final ScrollController scrollController = ScrollController(); + final RxBool showbtn = false.obs; + + @override + void onInit() { + super.onInit(); + storage = Storage( + Directory( + '${splashController.baseDirectory.value.path}/profiles/${splashController.currentProfile.value}', + ), + ); + serverCertExists = RxBool(storage.guiPemFiles.serverCertExists()); + addListenerToScrollController(); + _profileSet(); + loadDelayTask(); + initLanguageAndDarkMode(); + if (Platform.isAndroid) { + handleHomeWidgetClicked(); + } + } + + void addListenerToScrollController() { + scrollController.addListener(() { + //scroll listener + double showoffset = + 10.0; //Back to top botton will show on scroll offset 10.0 + + if (scrollController.offset > showoffset) { + showbtn.value = true; + } else { + showbtn.value = false; + } + }); + } + + void _profileSet() { + pendingFilter.value = Query(storage.tabs.tab()).getPendingFilter(); + if (!Query(storage.tabs.tab()).getWaitingFilter()) { + waitingFilter.value = Query(storage.tabs.tab()).getWaitingFilter(); + } else { + Query(storage.tabs.tab()).toggleWaitingFilter(); + waitingFilter.value = Query(storage.tabs.tab()).getWaitingFilter(); + } + projectFilter.value = Query(storage.tabs.tab()).projectFilter(); + tagUnion.value = Query(storage.tabs.tab()).tagUnion(); + selectedSort.value = Query(storage.tabs.tab()).getSelectedSort(); + selectedTags.addAll(Query(storage.tabs.tab()).getSelectedTags()); + + _refreshTasks(); + pendingTags.value = _pendingTags(); + projects.value = _projects(); + if (searchVisible.value) { + toggleSearch(); + } + } + + void _refreshTasks() { + if (pendingFilter.value) { + queriedTasks.value = storage.data + .pendingData() + .where((task) => task.status == 'pending') + .toList(); + } else { + queriedTasks.value = storage.data.completedData(); + } + + if (waitingFilter.value) { + var currentTime = DateTime.now(); + queriedTasks.value = queriedTasks + .where((task) => task.wait != null && task.wait!.isAfter(currentTime)) + .toList(); + } + + if (projectFilter.value.isNotEmpty) { + queriedTasks.value = queriedTasks.where((task) { + if (task.project == null) { + return false; + } else { + return task.project!.startsWith(projectFilter.value); + } + }).toList(); + } + + queriedTasks.value = queriedTasks.where((task) { + var tags = task.tags?.toSet() ?? {}; + if (tagUnion.value) { + if (selectedTags.isEmpty) { + return true; + } + return selectedTags.any((tag) => (tag.startsWith('+')) + ? tags.contains(tag.substring(1)) + : !tags.contains(tag.substring(1))); + } else { + return selectedTags.every((tag) => (tag.startsWith('+')) + ? tags.contains(tag.substring(1)) + : !tags.contains(tag.substring(1))); + } + }).toList(); + + var sortColumn = + selectedSort.value.substring(0, selectedSort.value.length - 1); + var ascending = selectedSort.value.endsWith('+'); + queriedTasks.sort((a, b) { + int result; + if (sortColumn == 'id') { + result = a.id!.compareTo(b.id!); + } else { + result = compareTasks(sortColumn)(a, b); + } + return ascending ? result : -result; + }); + + searchedTasks.assignAll(queriedTasks); + var searchTerm = searchController.text; + if (searchVisible.value) { + searchedTasks.value = searchedTasks + .where((task) => + task.description.contains(searchTerm) || + (task.annotations?.asList() ?? []).any( + (annotation) => annotation.description.contains(searchTerm))) + .toList(); + } + pendingTags.value = _pendingTags(); + projects.value = _projects(); + } + + Map _pendingTags() { + var frequency = tagFrequencies(storage.data.pendingData()); + var modified = tagsLastModified(storage.data.pendingData()); + var setOfTags = tagSet(storage.data.pendingData()); + + return SplayTreeMap.of({ + for (var tag in setOfTags) + tag: TagMetadata( + frequency: frequency[tag] ?? 0, + lastModified: modified[tag]!, + selected: selectedTags.contains('+$tag'), + ), + }); + } + + Map _projects() { + var frequencies = {}; + for (var task in storage.data.pendingData()) { + if (task.project != null) { + if (frequencies.containsKey(task.project)) { + frequencies[task.project!] = (frequencies[task.project] ?? 0) + 1; + } else { + frequencies[task.project!] = 1; + } + } + } + return SplayTreeMap.of(sparseDecoratedProjectTree(frequencies)); + } + + void togglePendingFilter() { + Query(storage.tabs.tab()).togglePendingFilter(); + pendingFilter.value = Query(storage.tabs.tab()).getPendingFilter(); + _refreshTasks(); + } + + void toggleWaitingFilter() { + Query(storage.tabs.tab()).toggleWaitingFilter(); + waitingFilter.value = Query(storage.tabs.tab()).getWaitingFilter(); + _refreshTasks(); + } + + void toggleProjectFilter(String project) { + Query(storage.tabs.tab()).toggleProjectFilter(project); + projectFilter.value = Query(storage.tabs.tab()).projectFilter(); + _refreshTasks(); + } + + void toggleTagUnion() { + Query(storage.tabs.tab()).toggleTagUnion(); + tagUnion.value = Query(storage.tabs.tab()).tagUnion(); + _refreshTasks(); + } + + void selectSort(String sort) { + Query(storage.tabs.tab()).setSelectedSort(sort); + selectedSort.value = Query(storage.tabs.tab()).getSelectedSort(); + _refreshTasks(); + } + + void toggleTagFilter(String tag) { + if (selectedTags.contains('+$tag')) { + selectedTags + ..remove('+$tag') + ..add('-$tag'); + } else if (selectedTags.contains('-$tag')) { + selectedTags.remove('-$tag'); + } else { + selectedTags.add('+$tag'); + } + Query(storage.tabs.tab()).toggleTagFilter(tag); + selectedTags.addAll(Query(storage.tabs.tab()).getSelectedTags()); + _refreshTasks(); + } + + Task getTask(String uuid) { + return storage.data.getTask(uuid); + } + + void mergeTask(Task task) { + storage.data.mergeTask(task); + + _refreshTasks(); + } + + Future synchronize(BuildContext context, bool isDialogNeeded) async { + try { + final connectivityResult = await Connectivity().checkConnectivity(); + if (connectivityResult == ConnectivityResult.none) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'You are not connected to the internet. Please check your network connection.', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2))); + } else { + if (isDialogNeeded) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return Dialog( + elevation: 5, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + child: Container( + padding: const EdgeInsets.all(16.0), + child: const Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16.0), + Text( + "Syncing", + // style: GoogleFonts.poppins( + // fontSize: TaskWarriorFonts.fontSizeLarge, + // fontWeight: TaskWarriorFonts.bold, + // ), + ), + SizedBox(height: 8.0), + Text( + "Please wait...", + // style: GoogleFonts.poppins( + // fontSize: TaskWarriorFonts.fontSizeSmall, + // fontWeight: TaskWarriorFonts.regular, + // ), + ), + ], + ), + ), + ); + }, + ); + } + + var header = await storage.home.synchronize(await client()); + _refreshTasks(); + pendingTags.value = _pendingTags(); + projects.value = _projects(); + + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + '${header['code']}: ${header['status']}', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2))); + + if (isDialogNeeded) { + Get.back(closeOverlays: true); + return; + } + } + } catch (e, trace) { + if (isDialogNeeded) { + Get.back(closeOverlays: true); + } + logError(e, trace); + } + } + + void toggleSortHeader() { + sortHeaderVisible.value = !sortHeaderVisible.value; + } + + void toggleSearch() { + searchVisible.value = !searchVisible.value; + if (!searchVisible.value) { + searchedTasks.assignAll(queriedTasks); + searchController.text = ''; + } + } + + void search(String term) { + searchedTasks.assignAll( + queriedTasks + .where( + (task) => + task.description.toLowerCase().contains(term.toLowerCase()), + ) + .toList(), + ); + } + + void setInitialTabIndex(int index) { + storage.tabs.setInitialTabIndex(index); + pendingFilter.value = Query(storage.tabs.tab()).getPendingFilter(); + waitingFilter.value = Query(storage.tabs.tab()).getWaitingFilter(); + selectedSort.value = Query(storage.tabs.tab()).getSelectedSort(); + selectedTags.addAll(Query(storage.tabs.tab()).getSelectedTags()); + projectFilter.value = Query(storage.tabs.tab()).projectFilter(); + _refreshTasks(); + } + + void addTab() { + storage.tabs.addTab(); + } + + List tabUuids() { + return storage.tabs.tabUuids(); + } + + int initialTabIndex() { + return storage.tabs.initialTabIndex(); + } + + void removeTab(int index) { + storage.tabs.removeTab(index); + pendingFilter.value = Query(storage.tabs.tab()).getPendingFilter(); + waitingFilter.value = Query(storage.tabs.tab()).getWaitingFilter(); + selectedSort.value = Query(storage.tabs.tab()).getSelectedSort(); + selectedTags.addAll(Query(storage.tabs.tab()).getSelectedTags()); + _refreshTasks(); + } + + void renameTab({ + required String tab, + required String name, + }) { + storage.tabs.renameTab(tab: tab, name: name); + } + + String? tabAlias(String tabUuid) { + return storage.tabs.alias(tabUuid); + } + + RxBool isSyncNeeded = false.obs; + + void checkForSync(BuildContext context) { + if (!isSyncNeeded.value) { + isNeededtoSyncOnStart(context); + isSyncNeeded.value = true; + } + } + + isNeededtoSyncOnStart(BuildContext context) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + bool? value; + value = prefs.getBool('sync-onStart') ?? false; + + if (value) { + synchronize(context, false); + } else {} + } + + RxBool syncOnStart = false.obs; + RxBool syncOnTaskCreate = false.obs; + RxBool delaytask = false.obs; + RxBool change24hr = false.obs; + + // dialogue box + + final formKey = GlobalKey(); + final namecontroller = TextEditingController(); + var due = Rxn(); + RxString dueString = ''.obs; + RxString priority = 'M'.obs; + final tagcontroller = TextEditingController(); + RxList tags = [].obs; + RxBool inThePast = false.obs; + + Filters getFilters() { + var selectedTagsMap = { + for (var tag in selectedTags) tag.substring(1): tag, + }; + + var keys = (pendingTags.keys.toSet()..addAll(selectedTagsMap.keys)).toList() + ..sort(); + + var tags = { + for (var tag in keys) + tag: TagFilterMetadata( + display: + '${selectedTagsMap[tag] ?? tag} ${pendingTags[tag]?.frequency ?? 0}', + selected: selectedTagsMap.containsKey(tag), + ), + }; + + var tagFilters = TagFilters( + tagUnion: tagUnion.value, + toggleTagUnion: toggleTagUnion, + tags: tags, + toggleTagFilter: toggleTagFilter, + ); + var filters = Filters( + pendingFilter: pendingFilter.value, + waitingFilter: waitingFilter.value, + togglePendingFilter: togglePendingFilter, + toggleWaitingFilter: toggleWaitingFilter, + projects: projects, + projectFilter: projectFilter.value, + toggleProjectFilter: toggleProjectFilter, + tagFilters: tagFilters, + ); + return filters; + } + + // select profile + void refreshTaskWithNewProfile() { + storage = Storage( + Directory( + '${splashController.baseDirectory.value.path}/profiles/${splashController.currentProfile.value}', + ), + ); + _refreshTasks(); + } + + RxBool useDelayTask = false.obs; + + Future loadDelayTask() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + useDelayTask.value = prefs.getBool('delaytask') ?? false; + } + + RxBool isDarkModeOn = false.obs; + + void initLanguageAndDarkMode() { + isDarkModeOn.value = AppSettings.isDarkMode; + selectedLanguage.value = AppSettings.selectedLanguage; + print("called and value is${isDarkModeOn.value}"); + } + + final addKey = GlobalKey(); + final searchKey1 = GlobalKey(); + final searchKey2 = GlobalKey(); + final filterKey = GlobalKey(); + final menuKey = GlobalKey(); + final refreshKey = GlobalKey(); + + late TutorialCoachMark tutorialCoachMark; + + void initInAppTour() { + tutorialCoachMark = TutorialCoachMark( + targets: addTargetsPage( + addKey: addKey, + searchKey: searchKey1, + filterKey: filterKey, + menuKey: menuKey, + refreshKey: refreshKey, + ), + colorShadow: TaskWarriorColors.black, + paddingFocus: 10, + opacityShadow: 0.8, + hideSkip: true, + onFinish: () { + SaveTourStatus.saveInAppTourStatus(true); + }); + } + + void showInAppTour(BuildContext context) { + Future.delayed( + const Duration(milliseconds: 500), + () { + SaveTourStatus.getInAppTourStatus().then((value) => { + if (value == false) + { + tutorialCoachMark.show(context: context), + } + else + { + // ignore: avoid_print + debugPrint('User has seen this page'), + // User has seen this page + } + }); + }, + ); + } + + final GlobalKey statusKey = GlobalKey(); + final GlobalKey projectsKey = GlobalKey(); + final GlobalKey filterTagKey = GlobalKey(); + final GlobalKey sortByKey = GlobalKey(); + + void initFilterDrawerTour() { + tutorialCoachMark = TutorialCoachMark( + targets: filterDrawer( + statusKey: statusKey, + projectsKey: projectsKey, + filterTagKey: filterTagKey, + sortByKey: sortByKey, + ), + colorShadow: TaskWarriorColors.black, + paddingFocus: 10, + opacityShadow: 1.00, + hideSkip: true, + onFinish: () { + SaveTourStatus.saveFilterTourStatus(true); + }, + ); + } + + void showFilterDrawerTour(BuildContext context) { + Future.delayed( + const Duration(milliseconds: 500), + () { + SaveTourStatus.getFilterTourStatus().then((value) => { + if (value == false) + { + tutorialCoachMark.show(context: context), + } + else + { + // ignore: avoid_print + print('User has seen this page'), + } + }); + }, + ); + } + + late RxString uuid = "".obs; + late RxBool isHomeWidgetTaskTapped = false.obs; + + void handleHomeWidgetClicked() async { + Uri? myUri = await HomeWidget.initiallyLaunchedFromHomeWidget(); + if (myUri != null) { + if (myUri.host == "cardclicked") { + if (myUri.queryParameters["uuid"] != null) { + uuid.value = myUri.queryParameters["uuid"] as String; + isHomeWidgetTaskTapped.value = true; + Future.delayed(Duration.zero, () { + Get.toNamed(Routes.DETAIL_ROUTE, arguments: ["uuid", uuid.value]); + }); + } + } + } + HomeWidget.widgetClicked.listen((uri) async { + if (uri != null) { + if (uri.host == "cardclicked") { + if (uri.queryParameters["uuid"] != null) { + uuid.value = uri.queryParameters["uuid"] as String; + isHomeWidgetTaskTapped.value = true; + } + debugPrint('uuid is $uuid'); + Get.toNamed(Routes.DETAIL_ROUTE, arguments: ["uuid", uuid.value]); + } + } + }); + } +} diff --git a/lib/controller/WidgetController.dart b/lib/app/modules/home/controllers/widget.controller.dart similarity index 64% rename from lib/controller/WidgetController.dart rename to lib/app/modules/home/controllers/widget.controller.dart index d705be9e..a618564f 100644 --- a/lib/controller/WidgetController.dart +++ b/lib/app/modules/home/controllers/widget.controller.dart @@ -5,22 +5,21 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:get/get.dart'; // Import the GetX package +import 'package:get/get.dart'; import 'package:home_widget/home_widget.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:taskwarrior/views/home/home.dart'; -import 'package:taskwarrior/widgets/taskdetails/profiles_widget.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/model/json/task.dart'; -import 'package:taskwarrior/model/storage.dart'; +import 'package:taskwarrior/app/models/filters.dart'; +import 'package:taskwarrior/app/models/json/task.dart'; +import 'package:taskwarrior/app/models/storage.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; +import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/urgency.dart'; // import 'package:taskwarrior/widgets/taskfunctions/datetime_differences.dart'; -import 'package:taskwarrior/widgets/taskfunctions/urgency.dart'; class WidgetController extends GetxController { final BuildContext? context; WidgetController(this.context); - late InheritedStorage storageWidget; - + final HomeController storageWidget = Get.find(); late Storage storage; late final Filters filters; RxList taskData = [].obs; // Use RxList for observable list @@ -31,13 +30,13 @@ class WidgetController extends GetxController { void fetchAllData() async { if (Platform.isAndroid || Platform.isIOS) { - storageWidget = StorageWidget.of(context!); // Use Get.context from GetX - var currentProfile = ProfilesWidget.of(context!).currentProfile; + // storageWidget = StorageWidget.of(context!); // Use Get.context from GetX + // var currentProfile = ProfilesWidget.of(context!).currentProfile; + var currentProfile = Get.find().currentProfile.value; - baseDirectory = ProfilesWidget.of(context!).getBaseDirectory(); - storage = + baseDirectory = Get.find().baseDirectory(); + storage = Storage(Directory('${baseDirectory!.path}/profiles/$currentProfile')); - allData.assignAll(storage.data.allData()); sendAndUpdate(); } @@ -56,13 +55,17 @@ class WidgetController extends GetxController { l.add({ "description": "$i.${task.description}", "urgency": 'urgencyLevel : ${urgency(task)}', - "uuid":task.uuid + "uuid": task.uuid }); i++; } } if (l.isEmpty) { - l.add({"description": "No tasks added yet.", "urgency": "urgencyLevel : 0","uuid":""}); + l.add({ + "description": "No tasks added yet.", + "urgency": "urgencyLevel : 0", + "uuid": "" + }); } await HomeWidget.saveWidgetData("tasks", jsonEncode(l)); } diff --git a/lib/widgets/add_Task.dart b/lib/app/modules/home/views/add_task_bottom_sheet.dart similarity index 70% rename from lib/widgets/add_Task.dart rename to lib/app/modules/home/views/add_task_bottom_sheet.dart index 2ace5132..6a8df7ce 100644 --- a/lib/widgets/add_Task.dart +++ b/lib/app/modules/home/views/add_task_bottom_sheet.dart @@ -1,62 +1,25 @@ -// ignore_for_file: library_private_types_in_public_api, use_build_context_synchronously, file_names - +// ignore_for_file: use_build_context_synchronously import 'dart:developer'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/controller/WidgetController.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/widgets/taskfunctions/taskparser.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; - -class AddTaskBottomSheet extends StatefulWidget { - const AddTaskBottomSheet({super.key}); - - @override - _AddTaskBottomSheetState createState() => _AddTaskBottomSheetState(); -} +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; +import 'package:taskwarrior/app/modules/home/controllers/widget.controller.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/taskparser.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; -class _AddTaskBottomSheetState extends State { - final formKey = GlobalKey(); - final namecontroller = TextEditingController(); - DateTime? due; - String dueString = ''; - String priority = 'M'; - final tagcontroller = TextEditingController(); - List tags = []; - bool inThePast = false; - bool change24hr = false; - @override - void initState() { - super.initState(); - checkto24hr(); - } - - Future checkto24hr() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - setState(() { - change24hr = prefs.getBool( - '24hourformate', - ) ?? - false; - }); - } - - @override - void dispose() { - tagcontroller.dispose(); - namecontroller.dispose(); - super.dispose(); - } +class AddTaskBottomSheet extends StatelessWidget { + final HomeController homeController; + const AddTaskBottomSheet({required this.homeController, super.key}); @override Widget build(BuildContext context) { - const title = 'Add Task'; return Scaffold( backgroundColor: Colors.transparent, body: Center( @@ -73,7 +36,7 @@ class _AddTaskBottomSheetState extends State { : TaskWarriorColors.kLightDialogBackGroundColor, title: Center( child: Text( - title, + 'Add Task', style: TextStyle( color: AppSettings.isDarkMode ? TaskWarriorColors.white @@ -82,7 +45,7 @@ class _AddTaskBottomSheetState extends State { ), ), content: Form( - key: formKey, + key: homeController.formKey, child: SizedBox( width: MediaQuery.of(context).size.width * 0.8, child: Column( @@ -100,7 +63,7 @@ class _AddTaskBottomSheetState extends State { ), ), actions: [ - buildCancelButton(context), + buildCancelButton(context, homeController), buildAddButton(context), ], ), @@ -113,16 +76,18 @@ class _AddTaskBottomSheetState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Wrap( - spacing: 8.0, - runSpacing: 4.0, - children: buildTagChips(), + Obx( + () => Wrap( + spacing: 8.0, + runSpacing: 4.0, + children: buildTagChips(), + ), ), Row( children: [ Expanded( child: TextFormField( - controller: tagcontroller, + controller: homeController.tagcontroller, style: TextStyle( color: AppSettings.isDarkMode ? TaskWarriorColors.white @@ -144,7 +109,7 @@ class _AddTaskBottomSheetState extends State { // Replace ElevatedButton with IconButton IconButton( onPressed: () { - addTag(tagcontroller.text.trim()); + addTag(homeController.tagcontroller.text.trim()); }, icon: const Icon(Icons.add), // Plus icon ), @@ -155,7 +120,7 @@ class _AddTaskBottomSheetState extends State { } List buildTagChips() { - return tags.map((tag) { + return homeController.tags.map((tag) { return InputChip( label: Text(tag), onDeleted: () { @@ -167,7 +132,7 @@ class _AddTaskBottomSheetState extends State { Widget buildName() => TextFormField( autofocus: true, - controller: namecontroller, + controller: homeController.namecontroller, style: TextStyle( color: AppSettings.isDarkMode ? TaskWarriorColors.white @@ -200,8 +165,9 @@ class _AddTaskBottomSheetState extends State { ), Expanded( child: GestureDetector( - child: TextFormField( - style: inThePast + child: Obx( + () => TextFormField( + style: homeController.inThePast.value ? TextStyle(color: TaskWarriorColors.red) : TextStyle( color: AppSettings.isDarkMode @@ -209,18 +175,19 @@ class _AddTaskBottomSheetState extends State { : TaskWarriorColors.black, ), readOnly: true, - controller: TextEditingController( - text: (due != null) ? dueString : null, - ), + controller: + TextEditingController(text: homeController.dueString.value), decoration: InputDecoration( hintText: 'Select due date', - hintStyle: inThePast + hintStyle: homeController.inThePast.value ? TextStyle(color: TaskWarriorColors.red) : TextStyle( color: AppSettings.isDarkMode ? TaskWarriorColors.white : TaskWarriorColors.black, ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12.0, vertical: 16.0), ), onTap: () async { var date = await showDatePicker( @@ -236,8 +203,6 @@ class _AddTaskBottomSheetState extends State { onSecondary: TaskWarriorColors.white, error: TaskWarriorColors.red, onError: TaskWarriorColors.black, - background: TaskWarriorColors.black, - onBackground: TaskWarriorColors.white, surface: TaskWarriorColors.black, onSurface: TaskWarriorColors.white, ) @@ -249,8 +214,6 @@ class _AddTaskBottomSheetState extends State { onSecondary: TaskWarriorColors.black, error: TaskWarriorColors.red, onError: TaskWarriorColors.white, - background: TaskWarriorColors.white, - onBackground: TaskWarriorColors.black, surface: TaskWarriorColors.white, onSurface: TaskWarriorColors.black, ), @@ -260,7 +223,7 @@ class _AddTaskBottomSheetState extends State { }, fieldHintText: "Month/Date/Year", context: context, - initialDate: due ?? DateTime.now(), + initialDate: homeController.due.value ?? DateTime.now(), firstDate: DateTime.now(), lastDate: DateTime(2037, 12, 31), ); @@ -275,12 +238,10 @@ class _AddTaskBottomSheetState extends State { brightness: Brightness.dark, primary: TaskWarriorColors.white, onPrimary: TaskWarriorColors.black, - secondary: TaskWarriorColors.black, + secondary: TaskWarriorColors.grey, onSecondary: TaskWarriorColors.white, error: TaskWarriorColors.red, onError: TaskWarriorColors.black, - background: TaskWarriorColors.black, - onBackground: TaskWarriorColors.white, surface: TaskWarriorColors.black, onSurface: TaskWarriorColors.white, ) @@ -288,27 +249,27 @@ class _AddTaskBottomSheetState extends State { brightness: Brightness.light, primary: TaskWarriorColors.black, onPrimary: TaskWarriorColors.white, - secondary: TaskWarriorColors.white, + secondary: TaskWarriorColors.grey, onSecondary: TaskWarriorColors.black, error: TaskWarriorColors.red, onError: TaskWarriorColors.white, - background: TaskWarriorColors.white, - onBackground: TaskWarriorColors.black, surface: TaskWarriorColors.white, onSurface: TaskWarriorColors.black, ), ), - child: MediaQuery( + child: Obx(() => MediaQuery( data: MediaQuery.of(context).copyWith( - alwaysUse24HourFormat: change24hr, + alwaysUse24HourFormat: + homeController.change24hr.value, ), - child: child!), + child: child!)), ); }, context: context, - initialTime: - TimeOfDay.fromDateTime(due ?? DateTime.now()), + initialTime: TimeOfDay.fromDateTime( + homeController.due.value ?? DateTime.now()), ); + print("date$date Time : $time"); if (time != null) { var dateTime = date.add( Duration( @@ -316,14 +277,17 @@ class _AddTaskBottomSheetState extends State { minutes: time.minute, ), ); - due = dateTime.toUtc(); - dueString = + print(dateTime); + homeController.due.value = dateTime.toUtc(); + + print("due value ${homeController.due}"); + homeController.dueString.value = DateFormat("dd-MM-yyyy HH:mm").format(dateTime); + print(homeController.dueString.value); if (dateTime.isBefore(DateTime.now())) { //Try changing the color. in the settings and Due display. - setState(() { - inThePast = true; - }); + + homeController.inThePast.value = true; ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( @@ -340,9 +304,7 @@ class _AddTaskBottomSheetState extends State { .kLightSecondaryBackgroundColor, duration: const Duration(seconds: 2))); } else { - setState(() { - inThePast = false; - }); + homeController.inThePast.value = false; } // setState(() {}); @@ -350,7 +312,7 @@ class _AddTaskBottomSheetState extends State { } }, ), - ), + )), ), ], ); @@ -371,42 +333,44 @@ class _AddTaskBottomSheetState extends State { ), textAlign: TextAlign.left, ), - DropdownButton( - dropdownColor: AppSettings.isDarkMode - ? TaskWarriorColors.kdialogBackGroundColor - : TaskWarriorColors.kLightDialogBackGroundColor, - value: priority, - elevation: 16, - style: GoogleFonts.poppins( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - underline: Container( - height: 1.5, - color: AppSettings.isDarkMode + Obx( + () => DropdownButton( + dropdownColor: AppSettings.isDarkMode ? TaskWarriorColors.kdialogBackGroundColor : TaskWarriorColors.kLightDialogBackGroundColor, + value: homeController.priority.value, + elevation: 16, + style: GoogleFonts.poppins( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + underline: Container( + height: 1.5, + color: AppSettings.isDarkMode + ? TaskWarriorColors.kdialogBackGroundColor + : TaskWarriorColors.kLightDialogBackGroundColor, + ), + onChanged: (String? newValue) { + homeController.priority.value = newValue!; + }, + items: ['H', 'M', 'L', 'None'] + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(' $value'), + ); + }).toList(), ), - onChanged: (String? newValue) { - setState(() { - priority = newValue!; - }); - }, - items: ['H', 'M', 'L', 'None'] - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(' $value'), - ); - }).toList(), ) ], ), ], ); - Widget buildCancelButton(BuildContext context) => TextButton( + Widget buildCancelButton( + BuildContext context, HomeController homeController) => + TextButton( child: Text( 'Cancel', style: TextStyle( @@ -415,11 +379,18 @@ class _AddTaskBottomSheetState extends State { : TaskWarriorColors.black, ), ), - onPressed: () => Navigator.of(context).pop("cancel"), + onPressed: () { + Navigator.of(context).pop("cancel"); + homeController.namecontroller.text = ''; + homeController.dueString.value = ""; + homeController.priority.value = 'M'; + homeController.tagcontroller.text = ''; + homeController.tags.value = []; + homeController.update(); + }, ); Widget buildAddButton(BuildContext context) { - WidgetController widgetController = Get.put(WidgetController(context)); return TextButton( child: Text( @@ -431,27 +402,39 @@ class _AddTaskBottomSheetState extends State { ), ), onPressed: () async { - if (formKey.currentState!.validate()) { + print(homeController.formKey.currentState); + if (homeController.formKey.currentState!.validate()) { try { - var task = taskParser(namecontroller.text) - .rebuild((b) => b..due = due) - .rebuild((p) => p..priority = priority); - if (tagcontroller.text != "") { - tags.add(tagcontroller.text.trim()); + var task = taskParser(homeController.namecontroller.text) + .rebuild((b) => b..due = homeController.due.value) + .rebuild((p) => p..priority = homeController.priority.value); + if (homeController.tagcontroller.text != "") { + homeController.tags.add(homeController.tagcontroller.text.trim()); + } + if (homeController.tags.isNotEmpty) { + task = task.rebuild((t) => t..tags.replace(homeController.tags)); } - if (tags.isNotEmpty) { - task = task.rebuild((t) => t..tags.replace(tags)); + Get.find().mergeTask(task); + // print(task); + + // StorageWidget.of(context).mergeTask(task); + homeController.namecontroller.text = ''; + homeController.dueString.value = ""; + homeController.priority.value = 'M'; + homeController.tagcontroller.text = ''; + homeController.tags.value = []; + homeController.update(); + // Navigator.of(context).pop(); + Get.back(); + if (Platform.isAndroid) { + WidgetController widgetController = + Get.put(WidgetController(context)); + widgetController.fetchAllData(); + + widgetController.update(); } - StorageWidget.of(context).mergeTask(task); - namecontroller.text = ''; - due = null; - priority = 'M'; - tagcontroller.text = ''; - tags = []; - setState(() {}); - Navigator.of(context).pop(); - widgetController.fetchAllData(); + homeController.update(); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( @@ -471,8 +454,9 @@ class _AddTaskBottomSheetState extends State { await SharedPreferences.getInstance(); bool? value; value = prefs.getBool('sync-OnTaskCreate') ?? false; - late InheritedStorage storageWidget; - storageWidget = StorageWidget.of(context); + // late InheritedStorage storageWidget; + // storageWidget = StorageWidget.of(context); + var storageWidget = Get.find(); if (value) { storageWidget.synchronize(context, true); } @@ -499,17 +483,13 @@ class _AddTaskBottomSheetState extends State { void addTag(String tag) { if (tag.isNotEmpty) { - setState(() { - String trimmedString = tag.trim(); - tags.add(trimmedString); - tagcontroller.text = ''; - }); + String trimmedString = tag.trim(); + homeController.tags.add(trimmedString); + homeController.tagcontroller.text = ''; } } void removeTag(String tag) { - setState(() { - tags.remove(tag); - }); + homeController.tags.remove(tag); } } diff --git a/lib/app/modules/home/views/filter_drawer_home_page.dart b/lib/app/modules/home/views/filter_drawer_home_page.dart new file mode 100644 index 00000000..37dd569e --- /dev/null +++ b/lib/app/modules/home/views/filter_drawer_home_page.dart @@ -0,0 +1,419 @@ +import 'package:flutter/material.dart'; +import 'package:get/get_state_manager/get_state_manager.dart'; +import 'package:taskwarrior/app/models/filters.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; +import 'package:taskwarrior/app/modules/home/views/project_column_home_page.dart'; +import 'package:taskwarrior/app/services/tag_filter.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class FilterDrawer extends StatelessWidget { + final Filters filters; + final HomeController homeController; + + const FilterDrawer( + {required this.filters, required this.homeController, super.key}); + + @override + Widget build(BuildContext context) { + homeController.initFilterDrawerTour(); + homeController.showFilterDrawerTour(context); + var tileColor = AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor; + return Drawer( + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + surfaceTintColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(8), + child: ListView( + primary: false, + key: const PageStorageKey('tags-filter'), + children: [ + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + Container( + height: 45, + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 0.0), + child: Text( + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerApplyFilters, + // style: GoogleFonts.poppins( + // fontWeight: TaskWarriorFonts.bold, + // color: (AppSettings.isDarkMode + // ? TaskWarriorColors.kprimaryTextColor + // : TaskWarriorColors.kLightPrimaryTextColor), + // fontSize: TaskWarriorFonts.fontSizeExtraLarge), + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + color: (AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor), + fontSize: TaskWarriorFonts.fontSizeExtraLarge, + ), + ), + ), + ), + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + Container( + // width: MediaQuery.of(context).size.width * 1, + // padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: tileColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: TaskWarriorColors.borderColor), + ), + child: ListTile( + contentPadding: const EdgeInsets.only( + left: 8, + ), + title: RichText( + key: homeController.statusKey, + maxLines: 2, + text: TextSpan( + children: [ + TextSpan( + text: + '${SentenceManager(currentLanguage: homeController.selectedLanguage.value).sentences.filterDrawerStatus} : ', + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + )), + TextSpan( + text: filters.pendingFilter + ? SentenceManager( + currentLanguage: homeController + .selectedLanguage.value) + .sentences + .filterDrawerPending + : SentenceManager( + currentLanguage: homeController + .selectedLanguage.value) + .sentences + .filterDrawerCompleted, + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + )), + ], + ), + ), + onTap: filters.togglePendingFilter, + textColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightSecondaryTextColor, + ), + ), + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + Container( + decoration: BoxDecoration( + color: tileColor, + borderRadius: BorderRadius.circular(2), + border: Border.all(color: TaskWarriorColors.borderColor), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: GestureDetector( + onTap: filters.toggleWaitingFilter, + child: Text( + !filters.waitingFilter + ? SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerShowWaiting + : SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerHideWaiting, + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightSecondaryTextColor, + )), + ), + ), + ), + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + Container( + key: homeController.projectsKey, + width: MediaQuery.of(context).size.width * 1, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: tileColor, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: TaskWarriorColors.borderColor, + ), + ), + child: ProjectsColumn( + projects: filters.projects, + projectFilter: filters.projectFilter, + callback: filters.toggleProjectFilter, + ), + ), + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + Container( + key: homeController.filterTagKey, + width: MediaQuery.of(context).size.width * 1, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: tileColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: TaskWarriorColors.borderColor), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 0.0), + child: Text( + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerFilterTagBy, + // style: GoogleFonts.poppins( + // color: (AppSettings.isDarkMode + // ? TaskWarriorColors.kprimaryTextColor + // : TaskWarriorColors.kLightSecondaryTextColor), + // // + // fontSize: TaskWarriorFonts.fontSizeLarge), + //textAlign: TextAlign.right, + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightSecondaryTextColor, + ), + ), + ), + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: TagFiltersWrap(filters.tagFilters), + ), + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + ], + ), + ), + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + Container( + key: homeController.sortByKey, + width: MediaQuery.of(context).size.width * 1, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: tileColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: TaskWarriorColors.borderColor), + ), + //height: 30, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 0.0), + child: Text( + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerSortBy, + // style: GoogleFonts.poppins( + // color: (AppSettings.isDarkMode + // ? TaskWarriorColors.kprimaryTextColor + // : TaskWarriorColors.kLightPrimaryTextColor), + // fontSize: TaskWarriorFonts.fontSizeLarge), + // textAlign: TextAlign.right, + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + )), + ), + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Wrap( + spacing: 8, + runSpacing: 4, + children: [ + for (var sort in [ + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerCreated, + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerModified, + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerStartTime, + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerDueTill, + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerPriority, + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerProject, + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerTags, + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerUrgency, + ]) + Obx( + () => ChoiceChip( + label: (homeController.selectedSort.value + .startsWith(sort)) + ? Text( + homeController.selectedSort.value, + ) + : Text(sort), + selected: false, + onSelected: (_) { + if (homeController.selectedSort == '$sort+') { + homeController.selectSort('$sort-'); + } else if (homeController.selectedSort == + '$sort-') { + homeController.selectSort(sort); + } else { + homeController.selectSort('$sort+'); + } + }, + // labelStyle: GoogleFonts.poppins( + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.black + // : TaskWarriorColors.white), + // backgroundColor: AppSettings.isDarkMode + // ? TaskWarriorColors + // .kLightSecondaryBackgroundColor + // : TaskWarriorColors.ksecondaryBackgroundColor, + ), + ) + ], + ), + ), + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + Container( + width: 200, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.kLightSecondaryBackgroundColor + // : TaskWarriorColors.ksecondaryBackgroundColor, + ), + child: TextButton( + onPressed: () { + if (homeController.selectedSort.value.endsWith('+') || + homeController.selectedSort.value.endsWith('-')) { + homeController.selectSort( + homeController.selectedSort.value.substring( + 0, + homeController.selectedSort.value.length - + 1)); + } + }, + child: Text( + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerResetSort, + // style: GoogleFonts.poppins( + // fontSize: TaskWarriorFonts.fontSizeMedium, + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.kLightSecondaryTextColor + // : TaskWarriorColors.ksecondaryTextColor), + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + )), + ), + ), + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + ], + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/app/modules/home/views/home_page_app_bar.dart b/lib/app/modules/home/views/home_page_app_bar.dart new file mode 100644 index 00000000..b739aa8f --- /dev/null +++ b/lib/app/modules/home/views/home_page_app_bar.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; + +import 'package:taskwarrior/app/routes/app_pages.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/taskserver/taskserver.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +import '../controllers/home_controller.dart'; + +class HomePageAppBar extends StatelessWidget implements PreferredSizeWidget { + final HomeController controller; + final Server? server; + final Credentials? credentials; + const HomePageAppBar( + {required this.server, + required this.credentials, + required this.controller, + super.key}); + + @override + Widget build(BuildContext context) { + return AppBar( + backgroundColor: TaskWarriorColors.kprimaryBackgroundColor, + surfaceTintColor: TaskWarriorColors.kprimaryBackgroundColor, + title: Center( + child: Obx( + () => Text( + SentenceManager(currentLanguage: controller.selectedLanguage.value) + .sentences + .homePageTitle, + style: TextStyle( + fontFamily: FontFamily.poppins, color: TaskWarriorColors.white), + ), + ), + ), + actions: [ + Obx( + () => IconButton( + key: controller.searchKey1, + icon: (controller.searchVisible.value) + ? Tooltip( + message: 'Cancel', + child: Icon(Icons.cancel, color: TaskWarriorColors.white)) + : Tooltip( + message: 'Search', + child: Icon(Icons.search, color: TaskWarriorColors.white)), + onPressed: controller.toggleSearch, + ), + ), + Builder( + builder: (context) => IconButton( + key: controller.refreshKey, + icon: Icon(Icons.refresh, color: TaskWarriorColors.white), + onPressed: () { + if (server != null || credentials != null) { + controller.synchronize(context, true); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Obx( + () => Text( + SentenceManager( + currentLanguage: + controller.selectedLanguage.value) + .sentences + .homePageTaskWarriorNotConfigured, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ), + action: SnackBarAction( + label: SentenceManager( + currentLanguage: + controller.selectedLanguage.value) + .sentences + .homePageSetup, + onPressed: () { + Get.toNamed(Routes.MANAGE_TASK_SERVER); + }, + textColor: TaskWarriorColors.purple, + ), + ), + ); + } + }, + ), + ), + Builder( + builder: (context) => IconButton( + key: controller.filterKey, + icon: Obx( + () => Tooltip( + message: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .homePageFilter, + child: Icon(Icons.filter_list, color: TaskWarriorColors.white), + ), + ), + onPressed: () => Scaffold.of(context).openEndDrawer(), + ), + ), + ], + leading: Builder( + builder: (context) => IconButton( + key: controller.menuKey, + icon: Obx( + () => Tooltip( + message: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .homePageMenu, + child: Icon(Icons.menu, color: TaskWarriorColors.white)), + ), + onPressed: () => Scaffold.of(context).openDrawer(), + ), + ), + ); + } + + @override + Size get preferredSize => AppBar().preferredSize; +} diff --git a/lib/app/modules/home/views/home_page_body.dart b/lib/app/modules/home/views/home_page_body.dart new file mode 100644 index 00000000..404cf341 --- /dev/null +++ b/lib/app/modules/home/views/home_page_body.dart @@ -0,0 +1,115 @@ +import 'package:double_back_to_close_app/double_back_to_close_app.dart'; +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; + +import 'package:taskwarrior/app/modules/home/views/tasks_builder.dart'; +import 'package:taskwarrior/app/utils/constants/palette.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +import '../controllers/home_controller.dart'; + +class HomePageBody extends StatelessWidget { + final HomeController controller; + const HomePageBody({required this.controller, super.key}); + + @override + Widget build(BuildContext context) { + controller.initInAppTour(); + controller.showInAppTour(context); + return DoubleBackToCloseApp( + snackBar: const SnackBar(content: Text('Tap back again to exit')), + // ignore: avoid_unnecessary_containers + child: Container( + color: AppSettings.isDarkMode + ? Palette.kToDark.shade200 + : TaskWarriorColors.white, + child: Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: Obx( + () => Column( + children: [ + if (controller.searchVisible.value) + Container( + margin: const EdgeInsets.symmetric( + horizontal: 10, vertical: 10), + child: SearchBar( + backgroundColor: WidgetStateProperty.all( + (TaskWarriorColors.kLightPrimaryBackgroundColor)), + surfaceTintColor: WidgetStateProperty.all( + (TaskWarriorColors.kLightPrimaryBackgroundColor)), + controller: controller.searchController, + // shape:, + onChanged: (value) { + controller.search(value); + }, + + shape: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.focused)) { + return RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + side: BorderSide( + color: TaskWarriorColors.black, + width: 2.0, + ), + ); + } else { + return RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + side: BorderSide( + color: TaskWarriorColors.black, + width: 1.5, + ), + ); + } + }, + ), + leading: const Icon(Icons.search_rounded), + trailing: [ + (controller.searchController.text.isNotEmpty) + ? IconButton( + key: GlobalKey(), + icon: Icon(Icons.cancel, + color: TaskWarriorColors.black), + onPressed: () { + controller.searchController.clear(); + controller + .search(controller.searchController.text); + }, + ) + : const SizedBox( + width: 0, + height: 0, + ) + ], + + hintText: 'Search', + ), + ), + Expanded( + child: Scrollbar( + child: Obx( + () => TasksBuilder( + // darkmode: AppSettings.isDarkMode, + useDelayTask: controller.useDelayTask.value, + taskData: controller.searchedTasks, + pendingFilter: controller.pendingFilter.value, + waitingFilter: controller.waitingFilter.value, + searchVisible: controller.searchVisible.value, + selectedLanguage: controller.selectedLanguage.value, + scrollController: controller.scrollController, + showbtn: controller.showbtn.value, + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/app/modules/home/views/home_page_floating_action_button.dart b/lib/app/modules/home/views/home_page_floating_action_button.dart new file mode 100644 index 00000000..2ddf18c7 --- /dev/null +++ b/lib/app/modules/home/views/home_page_floating_action_button.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; + +import 'package:taskwarrior/app/modules/home/views/add_task_bottom_sheet.dart'; + +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +import '../controllers/home_controller.dart'; + +class HomePageFloatingActionButton extends StatelessWidget { + final HomeController controller; + const HomePageFloatingActionButton({required this.controller,super.key}); + + @override + Widget build(BuildContext context) { + return FloatingActionButton( + key: controller.addKey, + heroTag: "btn3", + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kLightPrimaryBackgroundColor + : TaskWarriorColors.kprimaryBackgroundColor, + child: Tooltip( + message: 'Add Task', + child: Icon( + Icons.add, + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.white, + ), + ), + onPressed: () => showDialog( + context: context, + builder: (context) => AddTaskBottomSheet( + homeController: controller, + ), + ).then((value) { + if (controller.isSyncNeeded.value && value != "cancel") { + controller.isNeededtoSyncOnStart(context); + } + }) + // .then((value) { + // // print(value); + + // //if auto sync is turned on + // if (isSyncNeeded) { + // //if user have not created any event then + // //we won't call sync method + // if (value == "cancel") { + // } else { + // //else we can sync new tasks + // isNeededtoSyncOnStart(); + // } + // } + // }), + ); + } +} diff --git a/lib/app/modules/home/views/home_page_nav_drawer_menu_item.dart b/lib/app/modules/home/views/home_page_nav_drawer_menu_item.dart new file mode 100644 index 00000000..7425864f --- /dev/null +++ b/lib/app/modules/home/views/home_page_nav_drawer_menu_item.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; + +class NavDrawerMenuItem extends StatelessWidget { + final IconData icon; + final String text; + final VoidCallback onTap; + + const NavDrawerMenuItem({ + super.key, + required this.icon, + required this.text, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Container( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 10), + child: Row( + children: [ + Icon( + icon, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + const SizedBox(width: 10), + Text( + text, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + fontSize: TaskWarriorFonts.fontSizeMedium, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/app/modules/home/views/home_view.dart b/lib/app/modules/home/views/home_view.dart new file mode 100644 index 00000000..744a13c5 --- /dev/null +++ b/lib/app/modules/home/views/home_view.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; + +import 'package:taskwarrior/app/modules/home/views/filter_drawer_home_page.dart'; +import 'package:taskwarrior/app/modules/home/views/home_page_app_bar.dart'; +import 'package:taskwarrior/app/modules/home/views/home_page_body.dart'; +import 'package:taskwarrior/app/modules/home/views/home_page_floating_action_button.dart'; +import 'package:taskwarrior/app/modules/home/views/nav_drawer.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/taskserver/taskserver.dart'; +import 'package:taskwarrior/app/utils/home_path/home_path.dart' as rc; + +import '../controllers/home_controller.dart'; + +class HomeView extends GetView { + const HomeView({super.key}); + @override + Widget build(BuildContext context) { + Server? server; + Credentials? credentials; + + var contents = rc.Taskrc(controller.storage.home.home).readTaskrc(); + if (contents != null) { + server = Taskrc.fromString(contents).server; + credentials = Taskrc.fromString(contents).credentials; + } + + if (contents != null) { + server = Taskrc.fromString(contents).server; + credentials = Taskrc.fromString(contents).credentials; + } + + controller.checkForSync(context); + + // var taskData = controller.searchedTasks; + + // var pendingFilter = controller.pendingFilter; + // var waitingFilter = controller.waitingFilter; + // var pendingTags = controller.pendingTags; + + return Obx( + () => Scaffold( + appBar: HomePageAppBar( + server: server, + credentials: credentials, + controller: controller, + ), + backgroundColor: controller.isDarkModeOn.value + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + drawer: NavDrawer(homeController: controller), + body: HomePageBody(controller: controller), + endDrawer: Obx( + () => FilterDrawer( + filters: controller.getFilters(), + homeController: controller, + ), + ), + floatingActionButton: + HomePageFloatingActionButton(controller: controller), + resizeToAvoidBottomInset: false, + ), + ); + } +} diff --git a/lib/app/modules/home/views/nav_drawer.dart b/lib/app/modules/home/views/nav_drawer.dart new file mode 100644 index 00000000..cb9f4c62 --- /dev/null +++ b/lib/app/modules/home/views/nav_drawer.dart @@ -0,0 +1,221 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; +import 'package:taskwarrior/app/modules/home/views/home_page_nav_drawer_menu_item.dart'; +import 'package:taskwarrior/app/modules/home/views/theme_clipper.dart'; +import 'package:taskwarrior/app/routes/app_pages.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class NavDrawer extends StatelessWidget { + final HomeController homeController; + const NavDrawer({super.key, required this.homeController}); + + @override + Widget build(BuildContext context) { + return Drawer( + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + surfaceTintColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + child: Container( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + child: ListView( + padding: EdgeInsets.zero, + children: [ + Container( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + padding: const EdgeInsets.only(top: 50, left: 15, right: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + SentenceManager(currentLanguage: homeController.selectedLanguage.value) + .sentences + .homePageMenu, + style: TextStyle( + fontSize: TaskWarriorFonts.fontSizeExtraLarge, + fontWeight: TaskWarriorFonts.bold, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + Padding( + padding: const EdgeInsets.only(right: 10), + child: ThemeSwitcherClipper( + isDarkMode: AppSettings.isDarkMode, + onTap: (bool newMode) async { + AppSettings.isDarkMode = newMode; + await SelectedTheme.saveMode(AppSettings.isDarkMode); + Get.back(); + homeController.initLanguageAndDarkMode(); + }, + child: Icon( + AppSettings.isDarkMode + ? Icons.dark_mode + : Icons.light_mode, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + size: 15, + ), + ), + ), + ], + ), + ), + Container( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + height: Get.height * 0.03, + ), + Obx( + () => NavDrawerMenuItem( + icon: Icons.person_rounded, + text: SentenceManager( + currentLanguage: homeController.selectedLanguage.value, + ).sentences.navDrawerProfile, + onTap: () { + Get.toNamed(Routes.PROFILE); + }, + ), + ), + Obx( + () => NavDrawerMenuItem( + icon: Icons.summarize, + text: SentenceManager( + currentLanguage: homeController.selectedLanguage.value, + ).sentences.navDrawerReports, + onTap: () { + Get.toNamed(Routes.REPORTS); + }, + ), + ), + Obx( + () => NavDrawerMenuItem( + icon: Icons.info, + text: SentenceManager( + currentLanguage: homeController.selectedLanguage.value, + ).sentences.navDrawerAbout, + onTap: () { + Get.toNamed(Routes.ABOUT); + }, + ), + ), + Obx( + () => NavDrawerMenuItem( + icon: Icons.settings, + text: SentenceManager( + currentLanguage: homeController.selectedLanguage.value, + ).sentences.navDrawerSettings, + onTap: () async { + final SharedPreferences prefs = + await SharedPreferences.getInstance(); + homeController.syncOnStart.value = + prefs.getBool('sync-onStart') ?? false; + homeController.syncOnTaskCreate.value = + prefs.getBool('sync-OnTaskCreate') ?? false; + homeController.delaytask.value = + prefs.getBool('delaytask') ?? false; + homeController.change24hr.value = + prefs.getBool('24hourformate') ?? false; + + Get.toNamed(Routes.SETTINGS); + }, + ), + ), + Obx( + () => NavDrawerMenuItem( + icon: Icons.exit_to_app, + text: SentenceManager( + currentLanguage: homeController.selectedLanguage.value, + ).sentences.navDrawerExit, + onTap: () { + _showExitConfirmationDialog(context); + }, + ), + ), + ], + ), + ), + ); + } + + Future _showExitConfirmationDialog(BuildContext context) async { + return showDialog( + context: context, + barrierDismissible: + false, // Prevents closing the dialog by tapping outside + builder: (BuildContext context) { + return Utils.showAlertDialog( + title: Text( + SentenceManager(currentLanguage: homeController.selectedLanguage.value) + .sentences + .homePageExitApp, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + content: Text( + SentenceManager(currentLanguage: homeController.selectedLanguage.value) + .sentences + .homePageAreYouSureYouWantToExit, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + actions: [ + TextButton( + child: Text( + SentenceManager(currentLanguage: homeController.selectedLanguage.value) + .sentences + .homePageCancel, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + }, + ), + TextButton( + child: Text( + SentenceManager(currentLanguage: homeController.selectedLanguage.value) + .sentences + .homePageExit, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + SystemNavigator.pop(); // Exit the app + }, + ), + ], + ); + }, + ); + } +} diff --git a/lib/app/modules/home/views/project_column_home_page.dart b/lib/app/modules/home/views/project_column_home_page.dart new file mode 100644 index 00000000..57cd68d6 --- /dev/null +++ b/lib/app/modules/home/views/project_column_home_page.dart @@ -0,0 +1,258 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import 'package:google_fonts/google_fonts.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/projects.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class ProjectsColumn extends StatelessWidget { + const ProjectsColumn({ + required this.projects, + required this.projectFilter, + required this.callback, + super.key, + }); + + final Map projects; + final String projectFilter; + final void Function(String) callback; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Project : ", + // style: GoogleFonts.poppins( + // fontWeight: TaskWarriorFonts.bold, + // fontSize: TaskWarriorFonts.fontSizeMedium, + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.white + // : TaskWarriorColors.black, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + SizedBox( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + projectFilter == "" ? "Not selected" : projectFilter, + // style: GoogleFonts.poppins( + // fontSize: TaskWarriorFonts.fontSizeSmall, + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.white + // : TaskWarriorColors.black, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(left: 10, right: 10, top: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text("All Projects", + // style: GoogleFonts.poppins( + // fontWeight: TaskWarriorFonts.semiBold, + // fontSize: TaskWarriorFonts.fontSizeSmall, + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.white + // : TaskWarriorColors.black, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + )), + ], + ), + ), + if (projects.isNotEmpty) + ...projects.entries + .where((entry) => entry.value.parent == null) + .map((entry) => ProjectTile( + project: entry.key, + projects: projects, + projectFilter: projectFilter, + callback: callback, + )) + else + Column( + children: [ + Text( + "No Projects Found", + // style: GoogleFonts.poppins( + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.white + // : TaskWarriorColors.black, + // fontSize: TaskWarriorFonts.fontSizeSmall, + // ), + + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + SizedBox( + height: Get.height * 0.02, + ), + ], + ), + ], + ); + } +} + +class ProjectTile extends StatelessWidget { + const ProjectTile({ + required this.project, + required this.projects, + required this.projectFilter, + required this.callback, + super.key, + }); + + final String project; + final Map projects; + final String projectFilter; + final void Function(String) callback; + + @override + Widget build(BuildContext context) { + var node = projects[project]!; + + var title = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Flexible( + child: Text( + project, + style: GoogleFonts.poppins( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black), + ), + ), + Text( + (node.children.isEmpty) + ? '${node.subtasks}' + : '(${node.tasks}) ${node.subtasks}', + style: GoogleFonts.poppins( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black), + ) + ], + ); + + var radio = Radio( + activeColor: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.ksecondaryBackgroundColor, + focusColor: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.ksecondaryBackgroundColor, + toggleable: true, + value: project, + groupValue: projectFilter, + onChanged: (_) => callback(project), + ); + + return (node.children.isEmpty) + ? GestureDetector( + onTap: () => callback(project), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + radio, + Text( + project, + maxLines: 3, + // style: GoogleFonts.poppins( + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.white + // : TaskWarriorColors.black), + ), + const Spacer(), + Container( + padding: const EdgeInsets.symmetric(horizontal: 4), + margin: const EdgeInsets.only(right: 10), + decoration: const BoxDecoration( + // color: (AppSettings.isDarkMode + // ? TaskWarriorColors.white + // : TaskWarriorColors.ksecondaryBackgroundColor), + ), + child: Text( + (node.children.isEmpty) + ? '${node.subtasks}' + : '(${node.tasks}) ${node.subtasks}', + maxLines: 1, + style: GoogleFonts.poppins( + color: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + ), + ), + ), + ], + ), + ) + : ExpansionTile( + controlAffinity: ListTileControlAffinity.leading, + key: PageStorageKey(project), + leading: radio, + title: title, + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.ksecondaryBackgroundColor, + textColor: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.ksecondaryBackgroundColor, + children: node.children + .map((childProject) => ProjectTile( + project: childProject, + projects: projects, + projectFilter: projectFilter, + callback: callback, + )) + .toList(), + ); + } +} diff --git a/lib/app/modules/home/views/tag_filters_wrap.dart b/lib/app/modules/home/views/tag_filters_wrap.dart new file mode 100644 index 00000000..c9024406 --- /dev/null +++ b/lib/app/modules/home/views/tag_filters_wrap.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; + + +class TagFilterMetadata { + const TagFilterMetadata({ + required this.display, + required this.selected, + }); + + final String display; + final bool selected; +} + +class TagFilters { + const TagFilters({ + required this.tagUnion, + required this.toggleTagUnion, + required this.tags, + required this.toggleTagFilter, + }); + + final bool tagUnion; + final void Function() toggleTagUnion; + final Map tags; + final void Function(String) toggleTagFilter; +} + +class TagFiltersWrap extends StatelessWidget { + const TagFiltersWrap(this.filters, {super.key}); + + final TagFilters filters; + + @override + Widget build(BuildContext context) { + return Wrap( + spacing: 4, + children: [ + FilterChip( + onSelected: (_) => filters.toggleTagUnion(), + label: Text( + filters.tagUnion ? 'OR' : 'AND', + // style: GoogleFonts.poppins( + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.black + // : TaskWarriorColors.white, + // ), + ), + // backgroundColor: AppSettings.isDarkMode + // ? TaskWarriorColors.kLightSecondaryBackgroundColor + // : TaskWarriorColors.ksecondaryBackgroundColor, + ), + for (var entry in filters.tags.entries) + FilterChip( + onSelected: (_) => filters.toggleTagFilter(entry.key), + label: Text( + entry.value.display, + // style: GoogleFonts.poppins( + // fontWeight: entry.value.selected ? TaskWarriorFonts.bold : null, + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.black + // : TaskWarriorColors.white, + // ), + ), + // backgroundColor: AppSettings.isDarkMode + // ? TaskWarriorColors.kLightSecondaryBackgroundColor + // : TaskWarriorColors.kprimaryBackgroundColor, + ), + ], + ); + } +} diff --git a/lib/app/modules/home/views/tas_list_item.dart b/lib/app/modules/home/views/tas_list_item.dart new file mode 100644 index 00000000..95c4c369 --- /dev/null +++ b/lib/app/modules/home/views/tas_list_item.dart @@ -0,0 +1,222 @@ +import 'package:flutter/material.dart'; +import 'package:taskwarrior/app/models/json/task.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/language/supported_language.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/datetime_differences.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/modify.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/urgency.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class TaskListItem extends StatelessWidget { + const TaskListItem( + this.task, { + this.pendingFilter = false, + this.waitingFilter = false, + super.key, + required this.darkmode, + required this.useDelayTask, + required this.modify, + required this.selectedLanguage, + }); + + final Task task; + final bool pendingFilter; + final bool waitingFilter; + final bool darkmode; + final Modify modify; + final bool useDelayTask; + final SupportedLanguage selectedLanguage; + + @override + Widget build(BuildContext context) { + void saveChanges() async { + var now = DateTime.now().toUtc(); + modify.save( + modified: () => now, + ); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'Task Updated', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2))); + } + + bool isDueWithinOneDay(DateTime dueDate) { + DateTime now = DateTime.now(); + Duration difference = dueDate.difference(now); + return difference.inDays <= 1 && difference.inDays >= 0; + } + + MaterialColor colours = Colors.grey; + var colour = darkmode ? Colors.white : Colors.black; + var dimColor = darkmode + ? const Color.fromARGB(137, 248, 248, 248) + : const Color.fromARGB(136, 17, 17, 17); + + if (task.priority == 'H') { + colours = Colors.red; + } else if (task.priority == 'M') { + colours = Colors.yellow; + } else if (task.priority == 'L') { + colours = Colors.green; + } + + if ((task.status[0].toUpperCase()) == 'P') { + // Pending tasks + return Container( + decoration: BoxDecoration( + border: Border.all( + color: (task.due != null && + isDueWithinOneDay(task.due!) && + useDelayTask) + ? Colors + .red // Set border color to red if due within 1 day and useDelayTask is true + : dimColor, // Set default border color + ), + borderRadius: BorderRadius.circular(8.0), + ), + child: ListTile( + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + CircleAvatar( + backgroundColor: colours, + radius: 8, + ), + const SizedBox(width: 8), + SizedBox( + width: MediaQuery.of(context).size.width * 0.70, + child: Text( + '${(task.id == 0) ? '#' : task.id}. ${task.description}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + // style: GoogleFonts.poppins( + // color: colour, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, color: colour), + ), + ), + ], + ), + Text( + (task.annotations != null) + ? ' [${task.annotations!.length}]' + : '', + // style: GoogleFonts.poppins( + // color: colour, + // ), + style: TextStyle(fontFamily: FontFamily.poppins, color: colour), + ), + ], + ), + subtitle: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Text( + '${pendingFilter ? '' : '${task.status[0].toUpperCase()}\n'}' + '${SentenceManager(currentLanguage: selectedLanguage).sentences.homePageLastModified} : ${(task.modified != null) ? age(task.modified!) : ((task.start != null) ? age(task.start!) : '-')} | ' + '${SentenceManager(currentLanguage: selectedLanguage).sentences.homePageDue} : ${(task.due != null) ? when(task.due!) : '-'}' + .replaceFirst(RegExp(r' \[\]$'), '') + .replaceAll(RegExp(r' +'), ' '), + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontFamily: FontFamily.poppins, + color: dimColor, + fontSize: TaskWarriorFonts.fontSizeSmall), + ), + ), + ), + Text( + formatUrgency(urgency(task)), + // style: GoogleFonts.poppins( + // color: colour, + // ), + style: TextStyle(fontFamily: FontFamily.poppins, color: colour), + ), + ], + ), + ), + ); + } else { + // Completed tasks + return ListTile( + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + CircleAvatar( + backgroundColor: colours, + radius: 8, + ), + const SizedBox(width: 8), + SizedBox( + width: MediaQuery.of(context).size.width * 0.65, + child: Text( + '${(task.id == 0) ? '#' : task.id}. ${task.description}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + // style: GoogleFonts.poppins( + // color: colour, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, color: colour), + ), + ), + ], + ), + ], + ), + subtitle: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Text( + '${SentenceManager(currentLanguage: selectedLanguage).sentences.homePageLastModified} :${(task.modified != null) ? age(task.modified!) : ((task.start != null) ? age(task.start!) : '-')} | ' + '${SentenceManager(currentLanguage: selectedLanguage).sentences.homePageDue} : ${(task.due != null) ? when(task.due!) : '-'}' + .replaceFirst(RegExp(r' \[\]$'), '') + .replaceAll(RegExp(r' +'), ' '), + overflow: TextOverflow.ellipsis, + // style: GoogleFonts.poppins( + // color: dimColor, + // fontSize: TaskWarriorFonts.fontSizeSmall, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, + color: dimColor, + fontSize: TaskWarriorFonts.fontSizeSmall), + ), + ), + ), + Text( + formatUrgency(urgency(task)), + // style: GoogleFonts.poppins( + // color: colour, + // ), + style: TextStyle(fontFamily: FontFamily.poppins, color: colour), + ), + ], + ), + ); + } + } +} diff --git a/lib/widgets/buildTasks.dart b/lib/app/modules/home/views/tasks_builder.dart similarity index 50% rename from lib/widgets/buildTasks.dart rename to lib/app/modules/home/views/tasks_builder.dart index efed081e..7e0117c7 100644 --- a/lib/widgets/buildTasks.dart +++ b/lib/app/modules/home/views/tasks_builder.dart @@ -1,88 +1,65 @@ // ignore_for_file: file_names -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; +import 'dart:io'; +import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -import 'package:google_fonts/google_fonts.dart'; - -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/model/json.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/services/notification_services.dart'; -import 'package:taskwarrior/services/task_details.dart'; -import 'package:taskwarrior/services/task_list_tem.dart'; -import 'package:taskwarrior/widgets/taskfunctions/modify.dart'; - -import 'pallete.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/models/models.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; +import 'package:taskwarrior/app/modules/home/controllers/widget.controller.dart'; +import 'package:taskwarrior/app/modules/home/views/tas_list_item.dart'; +import 'package:taskwarrior/app/routes/app_pages.dart'; +import 'package:taskwarrior/app/services/notification_services.dart'; +import 'package:taskwarrior/app/utils/constants/palette.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/language/supported_language.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/modify.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; -class TasksBuilder extends StatefulWidget { - const TasksBuilder( - {super.key, - required this.taskData, - required this.pendingFilter, - required this.waitingFilter, - required this.searchVisible}); +class TasksBuilder extends StatelessWidget { + const TasksBuilder({ + super.key, + required this.taskData, + required this.pendingFilter, + required this.waitingFilter, + required this.useDelayTask, + required this.searchVisible, + required this.selectedLanguage, + required this.scrollController, + required this.showbtn, + }); final List taskData; final bool pendingFilter; final bool waitingFilter; final bool searchVisible; + final bool useDelayTask; + final SupportedLanguage selectedLanguage; + final ScrollController scrollController; + final bool showbtn; - @override - State createState() => _TasksBuilderState(); -} - -class _TasksBuilderState extends State { - late Modify modify; - ScrollController scrollController = ScrollController(); - bool showbtn = false; - Task? lastDeletedTask; - Task? lastCompletedTask; - bool isUndoInProgress = false; // track undo action - - @override - void initState() { - scrollController.addListener(() { - //scroll listener - double showoffset = - 10.0; //Back to top botton will show on scroll offset 10.0 - - if (scrollController.offset > showoffset) { - showbtn = true; - setState(() { - //update state - }); - } else { - showbtn = false; - setState(() { - //update state - }); - } - }); - super.initState(); - } - - void setStatus(String newValue, String id) { - var storageWidget = StorageWidget.of(context); - modify = Modify( + void setStatus(BuildContext context, String newValue, String id) { + var storageWidget = Get.find(); + Modify modify = Modify( getTask: storageWidget.getTask, mergeTask: storageWidget.mergeTask, uuid: id, ); modify.set('status', newValue); - saveChanges(); + saveChanges(context, modify, id, newValue); } - void saveChanges() async { + void saveChanges( + BuildContext context, Modify modify, String id, String newValue) async { var now = DateTime.now().toUtc(); modify.save( modified: () => now, ); - // Show a snackbar with an undo action ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( 'Task Updated', @@ -99,35 +76,46 @@ class _TasksBuilderState extends State { action: SnackBarAction( label: 'Undo', onPressed: () { - // Undo the task status change - undoChanges(); + undoChanges( + context, id, 'pending'); + }, ), )); } - void undoChanges() { - if (isUndoInProgress) { - return; // If undo is already in progress, do nothing - } - isUndoInProgress = true; - ScaffoldMessenger.of(context).removeCurrentSnackBar(); + void undoChanges(BuildContext context, String id, String originalValue) { + var storageWidget = Get.find(); + Modify modify = Modify( + getTask: storageWidget.getTask, + mergeTask: storageWidget.mergeTask, + uuid: id, + ); + modify.set('status', originalValue); + modify.save( + modified: () => DateTime.now().toUtc(), + ); + storageWidget.update(); + } - if (lastDeletedTask != null) { - setStatus('pending', lastDeletedTask!.uuid); - lastDeletedTask = null; - } + void cancelNotification(Task task) { + NotificationService notificationService = NotificationService(); - if (lastCompletedTask != null) { - setStatus('pending', lastCompletedTask!.uuid); - lastCompletedTask = null; + int notificationId = notificationService.calculateNotificationId( + task.due!, task.description, false, task.entry); + notificationService.cancelNotification(notificationId); + + if (task.wait != null) { + notificationId = notificationService.calculateNotificationId( + task.wait!, task.description, true, task.entry); + notificationService.cancelNotification(notificationId); } - isUndoInProgress = false; } - // final bool darkmode; @override Widget build(BuildContext context) { + // print(taskData); + var storageWidget = Get.find(); return Scaffold( floatingActionButtonLocation: FloatingActionButtonLocation.miniStartFloat, @@ -157,30 +145,42 @@ class _TasksBuilderState extends State { ), ), backgroundColor: Colors.transparent, - body: widget.taskData.isEmpty - ? Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: Text( - (widget.searchVisible) - ? 'Search Not Found :(' - : 'Click on the bottom right button to start adding tasks', - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontSize: TaskWarriorFonts.fontSizeLarge, - color: AppSettings.isDarkMode - ? TaskWarriorColors.kLightPrimaryBackgroundColor - : TaskWarriorColors.kprimaryBackgroundColor, + body: Obx( + () => taskData.isEmpty + ? Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: Text( + (searchVisible) + ? '${SentenceManager(currentLanguage: selectedLanguage).sentences.homePageSearchNotFound} :(' + : SentenceManager(currentLanguage: selectedLanguage) + .sentences + .homePageClickOnTheBottomRightButtonToStartAddingTasks, + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeLarge, + color: AppSettings.isDarkMode + ? TaskWarriorColors.kLightPrimaryBackgroundColor + : TaskWarriorColors.kprimaryBackgroundColor), + // style: GoogleFonts.poppins( + // fontSize: TaskWarriorFonts.fontSizeLarge, + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.kLightPrimaryBackgroundColor + // : TaskWarriorColors.kprimaryBackgroundColor, + // ), ), ), - ), - ) - : ListView( - controller: scrollController, - padding: EdgeInsets.only(top: 4, left: 2, right: 2, bottom: MediaQuery.of(context).size.height * 0.1), - children: [ - for (var task in widget.taskData) - widget.pendingFilter + ) + : ListView.builder( + padding: + const EdgeInsets.symmetric(vertical: 4, horizontal: 2), + itemCount: taskData.length, + controller: scrollController, + primary: false, + itemBuilder: (context, index) { + var task = taskData[index]; + return pendingFilter ? Slidable( key: ValueKey(task.uuid), startActionPane: ActionPane( @@ -189,22 +189,13 @@ class _TasksBuilderState extends State { SlidableAction( onPressed: (context) { // Complete task without confirmation - setStatus('completed', task.uuid); + setStatus(context, 'completed', task.uuid); if (task.due != null) { DateTime? dtb = task.due; dtb = dtb!.add(const Duration(minutes: 1)); - cancelNotification(task); - - if (kDebugMode) { - print("Task due is $dtb"); - print(dtb.day * 100 + - dtb.hour * 10 + - dtb.minute); - } } - lastCompletedTask = task; }, icon: Icons.done, label: "COMPLETE", @@ -218,23 +209,20 @@ class _TasksBuilderState extends State { SlidableAction( onPressed: (context) { // Delete task without confirmation - setStatus('deleted', task.uuid); + setStatus(context, 'deleted', task.uuid); if (task.due != null) { DateTime? dtb = task.due; dtb = dtb!.add(const Duration(minutes: 1)); - - //Task ID is set to null when creating the notification id. cancelNotification(task); + } + if (Platform.isAndroid) { + WidgetController widgetController = + Get.put(WidgetController(context)); + widgetController.fetchAllData(); - if (kDebugMode) { - print("Task due is $dtb"); - print(dtb.day * 100 + - dtb.hour * 10 + - dtb.minute); - } + widgetController.update(); } - lastDeletedTask = task; }, icon: Icons.delete, label: "DELETE", @@ -250,17 +238,28 @@ class _TasksBuilderState extends State { splashColor: AppSettings.isDarkMode ? TaskWarriorColors.black : TaskWarriorColors.borderColor, - onTap: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DetailRoute(task - .uuid), // added functionality for double tap to open task-details - ), - ), + onTap: () { + Get.toNamed(Routes.DETAIL_ROUTE, + arguments: ["uuid", task.uuid]); + }, + // child: Text(task.entry.toString()), + // onTap: () => Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => DetailRouteView(task.uuid), + // ), + // ), child: TaskListItem( task, - pendingFilter: widget.pendingFilter, + pendingFilter: pendingFilter, darkmode: AppSettings.isDarkMode, + useDelayTask: useDelayTask, + modify: Modify( + getTask: storageWidget.getTask, + mergeTask: storageWidget.mergeTask, + uuid: task.uuid, + ), + selectedLanguage: selectedLanguage, ), ), ), @@ -273,36 +272,27 @@ class _TasksBuilderState extends State { splashColor: AppSettings.isDarkMode ? TaskWarriorColors.black : TaskWarriorColors.borderColor, - onTap: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DetailRoute(task - .uuid), // added functionality for double tap to open task-details - ), - ), + onTap: () { + Get.toNamed(Routes.DETAIL_ROUTE, + arguments: ["uuid", task.uuid]); + }, + // child: Text(task.entry.toString()), child: TaskListItem( task, - pendingFilter: widget.pendingFilter, + pendingFilter: pendingFilter, darkmode: AppSettings.isDarkMode, + useDelayTask: useDelayTask, + modify: Modify( + getTask: storageWidget.getTask, + mergeTask: storageWidget.mergeTask, + uuid: task.uuid, + ), + selectedLanguage: selectedLanguage, ), ), - ), - ], - )); - } - - void cancelNotification(Task task) { - //Task ID is set to null when creating the notification id. - NotificationService notificationService = NotificationService(); - - int notificationId = notificationService.calculateNotificationId( - task.due!, task.description, false, task.entry); - notificationService.cancelNotification(notificationId); - - if (task.wait != null) { - notificationId = notificationService.calculateNotificationId( - task.wait!, task.description, true, task.entry); - notificationService.cancelNotification(notificationId); - } + ); + }, + ), + )); } } diff --git a/lib/config/theme_switcher_clipper.dart b/lib/app/modules/home/views/theme_clipper.dart similarity index 64% rename from lib/config/theme_switcher_clipper.dart rename to lib/app/modules/home/views/theme_clipper.dart index bcea6e6e..a00d9216 100644 --- a/lib/config/theme_switcher_clipper.dart +++ b/lib/app/modules/home/views/theme_clipper.dart @@ -1,9 +1,7 @@ -// ignore_for_file: library_private_types_in_public_api - import 'package:flutter/material.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; -class ThemeSwitcherClipper extends StatefulWidget { +class ThemeSwitcherClipper extends StatelessWidget { final bool isDarkMode; final Function(bool) onTap; @@ -14,16 +12,11 @@ class ThemeSwitcherClipper extends StatefulWidget { required Icon child, }); - @override - _ThemeSwitcherClipperState createState() => _ThemeSwitcherClipperState(); -} - -class _ThemeSwitcherClipperState extends State { @override Widget build(BuildContext context) { return GestureDetector( onTap: () async { - widget.onTap(!widget.isDarkMode); + onTap(!isDarkMode); }, child: ClipOval( child: Hero( @@ -35,9 +28,9 @@ class _ThemeSwitcherClipperState extends State { child: AnimatedSwitcher( duration: const Duration(seconds: 2), child: Icon( - widget.isDarkMode ? Icons.dark_mode : Icons.light_mode, + isDarkMode ? Icons.dark_mode : Icons.light_mode, key: UniqueKey(), - color: widget.isDarkMode + color: isDarkMode ? TaskWarriorColors.white : TaskWarriorColors.black, size: 40, diff --git a/lib/app/modules/manageTaskServer/bindings/manage_task_server_binding.dart b/lib/app/modules/manageTaskServer/bindings/manage_task_server_binding.dart new file mode 100644 index 00000000..e7a683fc --- /dev/null +++ b/lib/app/modules/manageTaskServer/bindings/manage_task_server_binding.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; + +import '../controllers/manage_task_server_controller.dart'; + +class ManageTaskServerBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => ManageTaskServerController(), + ); + } +} diff --git a/lib/app/modules/manageTaskServer/controllers/manage_task_server_controller.dart b/lib/app/modules/manageTaskServer/controllers/manage_task_server_controller.dart new file mode 100644 index 00000000..bbdde4ae --- /dev/null +++ b/lib/app/modules/manageTaskServer/controllers/manage_task_server_controller.dart @@ -0,0 +1,199 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:loggy/loggy.dart'; +import 'package:taskwarrior/app/models/storage.dart'; +import 'package:taskwarrior/app/models/storage/set_config.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; +import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/home_path/home_path.dart' as rc; +import 'package:taskwarrior/app/utils/taskserver/taskserver.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class ManageTaskServerController extends GetxController { + final HomeController homeController = Get.find(); + final SplashController splashController = Get.find(); + RxString profile = ''.obs; + late Storage storage; + late RxString alias; + Server? server; + Credentials? credentials; + final TextEditingController taskrcContentController = TextEditingController(); + RxBool isTaskDServerActive = true.obs; + RxBool hideKey = true.obs; + + @override + void onInit() { + super.onInit(); + storage = homeController.storage; + profile.value = storage.profile.uri.pathSegments + .lastWhere((segment) => segment.isNotEmpty); + alias = RxString(splashController.profilesMap[profile.value] ?? ''); + var contents = rc.Taskrc(storage.home.home).readTaskrc(); + if (contents != null) { + server = Taskrc.fromString(contents).server; + credentials = Taskrc.fromString(contents).credentials; + } + if (contents != null) { + server = Taskrc.fromString(contents).server; + credentials = Taskrc.fromString(contents).credentials; + } + configureCredentialString(); + update(); + } + + Future setConfigurationFromFixtureForDebugging() async { + try { + var contents = await rootBundle.loadString('assets/.taskrc'); + rc.Taskrc(storage.home.home).addTaskrc(contents); + var taskrc = Taskrc.fromString(contents); + server = taskrc.server; + credentials = taskrc.credentials; + for (var entry in { + 'taskd.certificate': '.task/first_last.cert.pem', + 'taskd.key': '.task/first_last.key.pem', + 'taskd.ca': '.task/ca.cert.pem', + // 'server.cert': '.task/server.cert.pem', + }.entries) { + var contents = await rootBundle.loadString('assets/${entry.value}'); + storage.guiPemFiles.addPemFile( + key: entry.key, + contents: contents, + name: entry.value.split('/').last, + ); + } + update(); + } catch (e) { + logError(e); + } + } + + /// The [setConfig] function is used to set the configuration of the TaskServer from the clipboard + void setContent(BuildContext context) async { + final clipboardData = await Clipboard.getData(Clipboard.kTextPlain); + taskrcContentController.text = clipboardData?.text ?? ''; + + // Check if the clipboard data is not empty + if (taskrcContentController.text.isNotEmpty) { + // Add the clipboard data to the taskrc file + storage.taskrc.addTaskrc(taskrcContentController.text); + + // Read the contents of the taskrc file + var contents = rc.Taskrc(storage.home.home).readTaskrc(); + + // Check if the contents were successfully read + if (contents != null) { + // Parse the contents into a Taskrc object + var taskrc = Taskrc.fromString(contents); + + // Check if the server and credentials are present in the Taskrc object + if (taskrc.server != null && taskrc.credentials != null) { + // Update the server and credentials variables + + server = taskrc.server; + credentials = taskrc.credentials; + update(); + + // Handle the case when server or credentials are missing in the Taskrc object + Navigator.pop(context); + + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'Success: Server or credentials are verified in taskrc file', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2))); + } else { + Navigator.pop(context); + // Handle the case when server or credentials are missing in the Taskrc object + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'Error: Server or credentials are missing in taskrc file', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2))); + } + } else { + Navigator.pop(context); + + // Handle the case when there is an error reading the taskrc file + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'Error: Failed to read taskrc file', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + // backgroundColor: AppSettings.isDarkMode + // ? TaskWarriorColors.ksecondaryBackgroundColor + // : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2))); + } + } + } + + RxString? credentialsString = RxString(''); + + void configureCredentialString() { + if (credentials != null) { + String key; + if (hideKey.value) { + key = credentials!.key.replaceAll(RegExp(r'[0-9a-f]'), '*'); + } else { + key = credentials!.key; + } + + credentialsString!.value = + '${credentials!.org}/${credentials!.user}/$key'; + + if (credentialsString!.value.isNotEmpty && server.toString().isNotEmpty) { + //print(credentialsString.value); + taskrcContentController.text = + "taskd.server=$server\ntaskd.credentials=${credentials!.org}/${credentials!.user}/$key"; + + isTaskDServerActive.value = false; + } + } + } + + void onTapPEMWidget(String pem, Storage storagePem) async { + if (pem == 'server.cert') { + storagePem.guiPemFiles.removeServerCert(); + Get.find().update(); + } else { + await setConfig( + storage: storagePem, + key: pem, + ); + update(); + } + } + + void onLongPressPEMWidget(String pem, String? name) { + if (pem != 'server.cert' && name != null) { + storage.guiPemFiles.removePemFile(pem); + update(); + } else { + null; + } + } +} diff --git a/lib/app/modules/manageTaskServer/views/manage_task_server_page_app_bar.dart b/lib/app/modules/manageTaskServer/views/manage_task_server_page_app_bar.dart new file mode 100644 index 00000000..0d05c240 --- /dev/null +++ b/lib/app/modules/manageTaskServer/views/manage_task_server_page_app_bar.dart @@ -0,0 +1,222 @@ +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:loggy/loggy.dart'; +import 'package:taskwarrior/app/models/storage/client.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; + +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../controllers/manage_task_server_controller.dart'; + +class ManageTaskServerPageAppBar extends StatelessWidget + implements PreferredSizeWidget { + final ManageTaskServerController controller; + const ManageTaskServerPageAppBar({required this.controller, super.key}); + + @override + Widget build(BuildContext context) { + return AppBar( + backgroundColor: TaskWarriorColors.kprimaryBackgroundColor, + titleSpacing: 0, + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Configure TaskServer", + style: TextStyle( + fontFamily: FontFamily.poppins, + color: TaskWarriorColors.white, + fontSize: TaskWarriorFonts.fontSizeLarge, + ), + ), + Text( + controller.alias.value == '' + ? controller.profile.value + : controller.alias.value, + // style: GoogleFonts.poppins( + // color: TaskWarriorColors.white, + // fontSize: TaskWarriorFonts.fontSizeSmall, + // ), + style: TextStyle( + fontFamily: FontFamily.poppins, + color: TaskWarriorColors.white, + fontSize: TaskWarriorFonts.fontSizeSmall, + ), + ), + ], + ), + actions: [ + IconButton( + icon: Icon( + Icons.info, + color: TaskWarriorColors.white, + ), + onPressed: () async { + String url = + "https://github.com/CCExtractor/taskwarrior-flutter?tab=readme-ov-file#taskwarrior-mobile-app"; + if (!await launchUrl(Uri.parse(url))) { + throw Exception('Could not launch $url'); + } + }, + ), + if (kDebugMode) + IconButton( + icon: Icon( + Icons.bug_report, + color: TaskWarriorColors.white, + ), + onPressed: controller.setConfigurationFromFixtureForDebugging, + ), + IconButton( + icon: Icon( + Icons.show_chart, + color: TaskWarriorColors.white, + ), + onPressed: () async { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return Utils.showAlertDialog( + title: Text( + 'Fetching statistics...', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 16), + Text( + 'Please wait...', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ); + }, + ); + + try { + // Fetch statistics header from storage + var header = + await controller.storage.home.statistics(await client()); + + // Determine the maximum key length for formatting purposes + var maxKeyLength = header.keys + .map((key) => (key as String).length) + .reduce(max); + + // Dismiss the loading dialog + // Navigator.of(context).pop(); + Get.back(); + + // Show statistics in a dialog + await showDialog( + context: context, + builder: (context) => Utils.showAlertDialog( + scrollable: true, + title: const Text( + 'Statistics:', + style: TextStyle(), + ), + content: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Display each key-value pair in the statistics header + for (var key in header.keys.toList()) + Text( + '${'$key:'.padRight(maxKeyLength + 1)} ${header[key]}', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ], + ), + ), + actions: [ + ElevatedButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + AppSettings.isDarkMode + ? TaskWarriorColors.kLightSecondaryBackgroundColor + : TaskWarriorColors.ksecondaryBackgroundColor, + ), + ), + onPressed: () { + // Navigator.of(context).pop(); + Get.back(); + }, + child: Text( + 'Ok', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + ), + ), + ), + ], + ), + ); + } on Exception catch (e, trace) { + // Dismiss the loading dialog + // Navigator.of(context).pop(); + Get.back(); + + //Displaying Error message. + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + trace.toString().startsWith("#0") + ? "Please set up your TaskServer." + : e.toString(), + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2))); + // Log the error and trace + logError(e, trace); + // Refresh the state of ProfilesWidget + } + }, + ), + ], + leading: BackButton( + color: TaskWarriorColors.white, + ), + ); + } + + @override + Size get preferredSize => AppBar().preferredSize; +} diff --git a/lib/app/modules/manageTaskServer/views/manage_task_server_page_body.dart b/lib/app/modules/manageTaskServer/views/manage_task_server_page_body.dart new file mode 100644 index 00000000..908dad93 --- /dev/null +++ b/lib/app/modules/manageTaskServer/views/manage_task_server_page_body.dart @@ -0,0 +1,475 @@ +// ignore_for_file: use_build_context_synchronously +import 'package:flutter/material.dart'; +import 'package:taskwarrior/app/modules/manageTaskServer/controllers/manage_task_server_controller.dart'; + +import 'package:get/get.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:taskwarrior/app/models/storage/set_config.dart'; +import 'package:taskwarrior/app/modules/manageTaskServer/views/pem_widget.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; + +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class ManageTaskServerPageBody extends StatelessWidget { + final ManageTaskServerController controller; + const ManageTaskServerPageBody({required this.controller, super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 20, right: 20), + child: ListView( + // mainAxisAlignment: MainAxisAlignment.start, + // crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Configure TASKRC", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + const SizedBox(height: 10), + GestureDetector( + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kdialogBackGroundColor + : TaskWarriorColors.kLightDialogBackGroundColor, + builder: (context) { + return StatefulBuilder( + builder: + (BuildContext context, StateSetter setState) { + // double heightOfModalBottomSheet = + // MediaQuery.of(context).size.height * 0.6; + + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Wrap( + children: [ + Container( + // height: heightOfModalBottomSheet, + padding: const EdgeInsets.all(16.0), + decoration: const BoxDecoration( + // color: tileColor, + borderRadius: BorderRadius.vertical( + top: Radius.circular(16.0)), + ), + + child: Column( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + Text( + 'Configure TaskRc', + style: TextStyle( + fontWeight: TaskWarriorFonts.bold, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + Text( + 'Paste the taskrc content or select taskrc file', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + const SizedBox(height: 16.0), + Padding( + padding: const EdgeInsets.all(12.0), + child: SizedBox( + height: Get.height * 0.15, + child: TextField( + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors + .black), + controller: controller + .taskrcContentController, + maxLines: 8, + decoration: InputDecoration( + counterStyle: TextStyle( + color: + AppSettings.isDarkMode + ? TaskWarriorColors + .white + : TaskWarriorColors + .black), + suffixIconConstraints: + const BoxConstraints( + maxHeight: 24, + maxWidth: 24, + ), + isDense: true, + suffix: IconButton( + onPressed: () async { + controller + .setContent(context); + }, + icon: const Icon( + Icons.content_paste), + ), + border: + const OutlineInputBorder(), + labelStyle: + GoogleFonts.poppins( + color: AppSettings + .isDarkMode + ? TaskWarriorColors + .white + : TaskWarriorColors + .black), + labelText: + 'Paste your taskrc contents here'), + ), + ), + ), + Text( + "Or", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + FilledButton.tonal( + style: ButtonStyle( + backgroundColor: AppSettings + .isDarkMode + ? WidgetStateProperty.all< + Color>( + TaskWarriorColors.black) + : WidgetStateProperty.all< + Color>( + TaskWarriorColors.white)), + onPressed: () async { + await setConfig( + storage: controller.storage, + key: 'TASKRC', + ); + setState(() {}); + Get.back(); + }, + child: Text( + 'Select TASKRC file', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ), + ], + ), + ), + ], + ), + ); + }, + ); + }, + ); + }, + child: Container( + width: MediaQuery.of(context).size.width * 1, + // height: MediaQuery.of(context).size.height * 0.05, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + // color: tileColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: TaskWarriorColors.borderColor), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + controller.taskrcContentController.text.isEmpty + ? "Set TaskRc" + : "Taskrc file is verified", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + Container( + height: 30, + width: 30, + decoration: BoxDecoration( + color: AppSettings.isDarkMode + ? TaskWarriorColors + .kLightSecondaryBackgroundColor + : TaskWarriorColors.ksecondaryBackgroundColor, + shape: BoxShape.circle, + ), + child: Center( + child: controller + .taskrcContentController.text.isNotEmpty + ? Icon( + Icons.check, + color: TaskWarriorColors.green, + ) + : Icon( + Icons.chevron_right_rounded, + color: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + Offstage( + offstage: controller.isTaskDServerActive.value, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "TaskD Server Info", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + const SizedBox(height: 10), + GestureDetector( + onTap: () {}, + child: Container( + width: MediaQuery.of(context).size.width * 1, + // height: MediaQuery.of(context).size.height * 0.05, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + // color: tileColor, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: TaskWarriorColors.borderColor, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + controller.server == null + ? Text( + 'Not Configured', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ) + : Text( + '${controller.server}', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + Container( + height: 30, + width: 30, + decoration: BoxDecoration( + color: AppSettings.isDarkMode + ? TaskWarriorColors + .kLightSecondaryBackgroundColor + : TaskWarriorColors + .ksecondaryBackgroundColor, + shape: BoxShape.circle, + ), + child: Center( + child: controller.server != null + ? Icon( + Icons.check, + color: TaskWarriorColors.green, + ) + : Icon( + Icons.chevron_right_rounded, + color: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "TaskD Server Credentials", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + const SizedBox(height: 10), + GestureDetector( + onTap: () {}, + child: Container( + width: MediaQuery.of(context).size.width * 1, + // height: MediaQuery.of(context).size.height * 0.05, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + // color: tileColor, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: TaskWarriorColors.borderColor), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + controller.credentialsString == null + ? Text( + 'Not Configured', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ) + : SizedBox( + width: + MediaQuery.of(context).size.width * + 0.7, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Text( + controller.credentialsString!.value, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ), + ), + GestureDetector( + onTap: () { + controller.hideKey.value = + !controller.hideKey.value; + }, + child: Container( + height: 30, + width: 30, + decoration: BoxDecoration( + color: AppSettings.isDarkMode + ? TaskWarriorColors + .kLightPrimaryBackgroundColor + : TaskWarriorColors + .kprimaryBackgroundColor, + shape: BoxShape.circle, + ), + child: controller.credentials == null + ? Icon( + Icons.chevron_right_rounded, + color: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + ) + : Icon( + controller.hideKey.value + ? Icons.visibility_off + : Icons.visibility, + color: AppSettings.isDarkMode + ? TaskWarriorColors.green + : TaskWarriorColors.green, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ], + )), + GetBuilder( + builder: (controller) { + List pemWidgets = []; + for (var pem in [ + 'taskd.certificate', + 'taskd.key', + 'taskd.ca', + if (controller.homeController.serverCertExists.value) + 'server.cert', + ]) { + pemWidgets.add( + PemWidget( + storage: controller.storage, + pem: pem, + optionString: pem == "taskd.certificate" + ? "Configure your certificate" + : pem == "taskd.key" + ? "Configure TaskServer key" + : pem == "taskd.ca" + ? "Configure Server Certificate" + : "Configure Server Certificate", + listTileTitle: pem == "taskd.certificate" + ? "Select Certificate" + : pem == "taskd.key" + ? "Select key" + : pem == "taskd.ca" + ? "Select Certificate" + : "Select Certificate", + onTapCallBack: controller.onTapPEMWidget, + onLongPressCallBack: controller.onLongPressPEMWidget, + ), + ); + } + return Column( + children: pemWidgets, + ); + }, + ) + ], + ), + ); + } +} diff --git a/lib/app/modules/manageTaskServer/views/manage_task_server_view.dart b/lib/app/modules/manageTaskServer/views/manage_task_server_view.dart new file mode 100644 index 00000000..2aa071e5 --- /dev/null +++ b/lib/app/modules/manageTaskServer/views/manage_task_server_view.dart @@ -0,0 +1,27 @@ +// ignore_for_file: use_build_context_synchronously + + +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/manageTaskServer/views/manage_task_server_page_app_bar.dart'; +import 'package:taskwarrior/app/modules/manageTaskServer/views/manage_task_server_page_body.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; + +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +import '../controllers/manage_task_server_controller.dart'; + +class ManageTaskServerView extends GetView { + const ManageTaskServerView({super.key}); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: ManageTaskServerPageAppBar(controller: controller), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + body: ManageTaskServerPageBody(controller: controller), + ); + } +} diff --git a/lib/app/modules/manageTaskServer/views/pem_widget.dart b/lib/app/modules/manageTaskServer/views/pem_widget.dart new file mode 100644 index 00000000..a101b9e5 --- /dev/null +++ b/lib/app/modules/manageTaskServer/views/pem_widget.dart @@ -0,0 +1,187 @@ +import 'package:crypto/crypto.dart'; +import 'package:flutter/material.dart'; +import 'package:pem/pem.dart'; +import 'package:taskwarrior/app/models/storage.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class PemWidget extends StatelessWidget { + const PemWidget({ + required this.storage, + required this.pem, + super.key, + required this.optionString, + required this.listTileTitle, + required this.onTapCallBack, + required this.onLongPressCallBack, + // required this.manageTaskServerController, + }); + + final Storage storage; + final String pem; + final String optionString; + final String listTileTitle; + final Function(String pem, Storage storagePem) onTapCallBack; + final Function(String pem, String? name) onLongPressCallBack; + // final ManageTaskServerController manageTaskServerController; + + @override + Widget build(BuildContext context) { + String fingerprint(String pemContents) { + var firstCertificateBlock = decodePemBlocks( + PemLabel.certificate, + pemContents, + ).first; + + return '${sha1.convert(firstCertificateBlock)}'; + } + + var contents = storage.guiPemFiles.pemContents(pem); + var name = storage.guiPemFiles.pemFilename(pem); + String identifier = ""; + try { + if (contents != null) { + identifier = fingerprint(contents).toUpperCase(); // Null check removed + } + } catch (e) { + debugPrint(e.toString()); + } + + var tileColor = AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor; + return Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + optionString, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + const SizedBox( + height: 10, + ), + GestureDetector( + onTap: () { + onTapCallBack(pem, storage); + }, + // onTap: () { + // manageTaskServerController.onTapPEMWidget(pem, storage); + // }, + // onTap: (pem == 'server.cert') + // ? () { + // widget.storage.guiPemFiles.removeServerCert(); + // ProfilesWidget.of(context).setState( + // () {}, + // ); + // setState( + // () {}, + // ); + // } + // : () async { + // await setConfig( + // storage: widget.storage, + // key: widget.pem, + // ); + // setState( + // () {}, + // ); + // }, + // onLongPress: () { + // manageTaskServerController.onLongPressPEMWidget(pem, name); + // }, + onLongPress: () { + onLongPressCallBack(pem, name); + }, + // onLongPress: (widget.pem != 'server.cert' && name != null) + // ? () { + // widget.storage.guiPemFiles.removePemFile(widget.pem); + // setState(() {}); + // } + // : null, + child: Container( + width: MediaQuery.of(context).size.width * 1, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: tileColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: TaskWarriorColors.borderColor), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width * 0.7, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Text( + name == null + ? listTileTitle + : (pem == 'server.cert') + ? '' + : "$name = ", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + Text( + pem == 'taskd.key' + ? name != null + ? "private.key.pem" + : "" + : identifier, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ), + ), + Container( + height: 30, + width: 30, + decoration: BoxDecoration( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kLightSecondaryBackgroundColor + : TaskWarriorColors.ksecondaryBackgroundColor, + shape: BoxShape.circle, + ), + child: Center( + child: name == null + ? Icon( + Icons.chevron_right_rounded, + color: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + ) + : Icon( + Icons.check, + color: TaskWarriorColors.green, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/app/modules/onboarding/bindings/onboarding_binding.dart b/lib/app/modules/onboarding/bindings/onboarding_binding.dart new file mode 100644 index 00000000..d1eb1edf --- /dev/null +++ b/lib/app/modules/onboarding/bindings/onboarding_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/onboarding/controllers/onboarding_controller.dart'; + +class OnboardingBinding extends Bindings { + @override + void dependencies() { + Get.put( + OnboardingController(), + ); + } +} diff --git a/lib/app/modules/onboarding/controllers/onboarding_controller.dart b/lib/app/modules/onboarding/controllers/onboarding_controller.dart new file mode 100644 index 00000000..fcde375c --- /dev/null +++ b/lib/app/modules/onboarding/controllers/onboarding_controller.dart @@ -0,0 +1,15 @@ +import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class OnboardingController extends GetxController { + RxInt currentPage = 0.obs; + int get getCurrentPage => currentPage.value; + + void setCurrentPage(int newCurrentPage) => currentPage.value = newCurrentPage; + // Might cause issue + Future markOnboardingAsCompleted() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setBool('onboarding_completed', true); + update(); + } +} diff --git a/lib/app/modules/onboarding/views/onboarding_page.dart b/lib/app/modules/onboarding/views/onboarding_page.dart new file mode 100644 index 00000000..57b1cbef --- /dev/null +++ b/lib/app/modules/onboarding/views/onboarding_page.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/models/models.dart'; +import 'package:taskwarrior/app/utils/constants/constants.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; + +class OnboardingPage extends StatelessWidget { + final OnboardingModel onboardingModel; + const OnboardingPage({super.key, required this.onboardingModel}); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(35.0), + child: Column( + children: [ + SvgPicture.asset( + onboardingModel.image, + height: Get.width / 100 * 30, + ), + SizedBox( + height: (Get.height >= 840) ? 60 : 30, + ), + Text( + onboardingModel.title, + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: TaskWarriorFonts.semiBold, + fontFamily: FontFamily.poppins, + fontSize: (Get.width <= 550) ? 30 : 35, + color: Colors.black, + ), + ), + SizedBox( + height: Get.height * 2 / 100, + ), + Text( + onboardingModel.desc, + style: TextStyle( + fontWeight: TaskWarriorFonts.light, + fontSize: (Get.width <= 550) ? 17 : 17, + color: Colors.black, + ), + textAlign: TextAlign.center, + ), + SizedBox( + height: Get.height * 2 / 100, + ) + ], + ), + ), + ); + } +} diff --git a/lib/app/modules/onboarding/views/onboarding_page_bottom_section.dart b/lib/app/modules/onboarding/views/onboarding_page_bottom_section.dart new file mode 100644 index 00000000..4fd50daa --- /dev/null +++ b/lib/app/modules/onboarding/views/onboarding_page_bottom_section.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:taskwarrior/app/modules/onboarding/controllers/onboarding_controller.dart'; +import 'package:taskwarrior/app/modules/onboarding/views/onboarding_page_start_button.dart'; +import 'package:taskwarrior/app/modules/onboarding/views/onborading_page_animated_container.dart'; +import 'package:taskwarrior/app/modules/onboarding/views/onborading_page_next_button.dart'; +import 'package:taskwarrior/app/utils/constants/constants.dart'; + +class OnboardingPageBottomSection extends StatelessWidget { + final OnboardingController controller; + final PageController pageController; + const OnboardingPageBottomSection({super.key, required this.controller,required this.pageController}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + contents.length, + (int index) => OnboardingPageAnimatedContainer( + controller: controller, + index: index, + ), + ), + ), + controller.getCurrentPage + 1 == contents.length + ? OnboardingPageStartButton(controller: controller) + : OnboardingPageNextButton( + pageController: pageController, + ), + ], + ); + } +} diff --git a/lib/app/modules/onboarding/views/onboarding_page_start_button.dart b/lib/app/modules/onboarding/views/onboarding_page_start_button.dart new file mode 100644 index 00000000..972a1314 --- /dev/null +++ b/lib/app/modules/onboarding/views/onboarding_page_start_button.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/onboarding/controllers/onboarding_controller.dart'; +import 'package:taskwarrior/app/routes/app_pages.dart'; +import 'package:taskwarrior/app/utils/constants/constants.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; + +class OnboardingPageStartButton extends StatelessWidget { + final OnboardingController controller; + const OnboardingPageStartButton({super.key, required this.controller}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(30), + child: ElevatedButton( + onPressed: () { + controller.markOnboardingAsCompleted(); + // Navigator.of(context).pushAndRemoveUntil( + // MaterialPageRoute(builder: (context) => const HomeView()), + // (Route route) => false, + // ); + Get.toNamed(Routes.HOME); + }, + style: ElevatedButton.styleFrom( + backgroundColor: TaskWarriorColors.black, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + padding: (Get.width <= 550) + ? const EdgeInsets.symmetric(horizontal: 100, vertical: 20) + : EdgeInsets.symmetric(horizontal: Get.width * 0.2, vertical: 25), + textStyle: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: (Get.width <= 550) ? 13 : 17, + ), + ), + child: Text( + "Start", + style: TextStyle( + fontWeight: TaskWarriorFonts.light, + fontSize: (Get.width <= 550) ? 17 : 17, + color: Colors.white, + ), + ), + ), + ); + } +} diff --git a/lib/app/modules/onboarding/views/onboarding_view.dart b/lib/app/modules/onboarding/views/onboarding_view.dart new file mode 100644 index 00000000..5c0d2d36 --- /dev/null +++ b/lib/app/modules/onboarding/views/onboarding_view.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/onboarding/views/onboarding_page.dart'; +import 'package:taskwarrior/app/modules/onboarding/views/onboarding_page_bottom_section.dart'; +import 'package:taskwarrior/app/utils/constants/constants.dart'; + +import '../controllers/onboarding_controller.dart'; + +class OnboardingView extends GetView { + const OnboardingView({super.key}); + @override + Widget build(BuildContext context) { + double height = Get.height; + + PageController pageController = PageController(); + return Obx(() => Scaffold( + backgroundColor: contents[controller.getCurrentPage].colors, + body: SafeArea( + child: Column( + children: [ + Expanded( + flex: 3, + child: PageView.builder( + physics: const BouncingScrollPhysics(), + controller: pageController, + onPageChanged: (value) { + controller.setCurrentPage(value); + }, + itemCount: contents.length, + itemBuilder: (context, i) { + return OnboardingPage(onboardingModel: contents[i]); + }, + ), + ), + SizedBox( + height: height * 5 / 100, + ), + Expanded( + flex: 1, + child: SingleChildScrollView( + child: OnboardingPageBottomSection( + controller: controller, + pageController: pageController, + ), + ), + ), + ], + ), + ), + ),); + } +} diff --git a/lib/app/modules/onboarding/views/onborading_page_animated_container.dart b/lib/app/modules/onboarding/views/onborading_page_animated_container.dart new file mode 100644 index 00000000..d8f68768 --- /dev/null +++ b/lib/app/modules/onboarding/views/onborading_page_animated_container.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:taskwarrior/app/modules/onboarding/controllers/onboarding_controller.dart'; +import 'package:taskwarrior/app/utils/constants/constants.dart'; + +class OnboardingPageAnimatedContainer extends StatelessWidget { + final OnboardingController controller; + final int index; + const OnboardingPageAnimatedContainer( + {super.key, required this.controller, required this.index}); + + @override + Widget build(BuildContext context) { + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(50)), + color: TaskWarriorColors.black, + ), + margin: const EdgeInsets.only(right: 5), + height: 10, + curve: Curves.easeIn, + width: controller.getCurrentPage == index ? 20 : 10, + ); + } +} diff --git a/lib/app/modules/onboarding/views/onborading_page_next_button.dart b/lib/app/modules/onboarding/views/onborading_page_next_button.dart new file mode 100644 index 00000000..f5ddf80b --- /dev/null +++ b/lib/app/modules/onboarding/views/onborading_page_next_button.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/utils/constants/constants.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; + +class OnboardingPageNextButton extends StatelessWidget { + final PageController pageController; + const OnboardingPageNextButton({super.key , required this.pageController}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(30), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + pageController.jumpToPage(2); + }, + style: TextButton.styleFrom( + elevation: 0, + textStyle: TextStyle( + fontWeight: TaskWarriorFonts.semiBold, + fontSize: (Get.width <= 550) ? 13 : 17, + ), + ), + child: Text( + "Skip", + style: TextStyle( + fontWeight: TaskWarriorFonts.bold, + color: TaskWarriorColors.black, + fontFamily: FontFamily.poppins, + fontSize: (Get.width <= 550) ? 12 : 12, + ), + ), + ), + ElevatedButton( + onPressed: () { + pageController.nextPage( + duration: const Duration(milliseconds: 200), + curve: Curves.easeIn, + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: TaskWarriorColors.black, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + elevation: 0, + padding: (Get.width <= 550) + ? const EdgeInsets.symmetric(horizontal: 30, vertical: 20) + : const EdgeInsets.symmetric(horizontal: 30, vertical: 25), + textStyle: TextStyle(fontSize: (Get.width <= 550) ? 13 : 17), + ), + child: Text( + "Next", + style: TextStyle( + fontWeight: TaskWarriorFonts.light, + color: TaskWarriorColors.white, + fontFamily: FontFamily.poppins, + fontSize: (Get.width <= 550) ? 12 : 12, + ), + + ), + ), + ], + ), + ); + } +} diff --git a/lib/app/modules/profile/bindings/profile_binding.dart b/lib/app/modules/profile/bindings/profile_binding.dart new file mode 100644 index 00000000..92960891 --- /dev/null +++ b/lib/app/modules/profile/bindings/profile_binding.dart @@ -0,0 +1,16 @@ +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart'; + +import '../controllers/profile_controller.dart'; + +class ProfileBinding extends Bindings { + @override + void dependencies() { + Get.put( + ProfileController(), + ); + Get.put( + SplashController(), + ); + } +} diff --git a/lib/app/modules/profile/controllers/profile_controller.dart b/lib/app/modules/profile/controllers/profile_controller.dart new file mode 100644 index 00000000..7280eea3 --- /dev/null +++ b/lib/app/modules/profile/controllers/profile_controller.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart'; +import 'package:taskwarrior/app/tour/profile_page_tour.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; + +class ProfileController extends GetxController { + var profilesWidget = Get.find(); + late RxMap profilesMap; + late RxString currentProfile; + @override + void onInit() { + profilesMap = profilesWidget.profilesMap; + currentProfile = profilesWidget.currentProfile; + super.onInit(); + } + + late TutorialCoachMark tutorialCoachMark; + + final GlobalKey currentProfileKey = GlobalKey(); + + final GlobalKey addNewProfileKey = GlobalKey(); + + void initProfilePageTour() { + tutorialCoachMark = TutorialCoachMark( + targets: addProfilePage( + currentProfileKey: currentProfileKey, + + addNewProfileKey: addNewProfileKey, + ), + colorShadow: TaskWarriorColors.black, + paddingFocus: 10, + opacityShadow: 1.00, + hideSkip: true, + onFinish: () { + SaveTourStatus.saveProfileTourStatus(true); + }, + ); + } + + void showProfilePageTour(BuildContext context) { + Future.delayed( + const Duration(milliseconds: 500), + () { + SaveTourStatus.getProfileTourStatus().then((value) => { + if (value == false) + { + tutorialCoachMark.show(context: context), + } + else + { + // ignore: avoid_print + print('User has seen this page'), + } + }); + }, + ); + } +} diff --git a/lib/widgets/profilefunctions/deleteprofiledialog.dart b/lib/app/modules/profile/views/deleteprofiledialog.dart similarity index 85% rename from lib/widgets/profilefunctions/deleteprofiledialog.dart rename to lib/app/modules/profile/views/deleteprofiledialog.dart index 50d58654..eb80a298 100644 --- a/lib/widgets/profilefunctions/deleteprofiledialog.dart +++ b/lib/app/modules/profile/views/deleteprofiledialog.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; - -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/utility/utilities.dart'; -import 'package:taskwarrior/widgets/taskdetails/profiles_widget.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; class DeleteProfileDialog extends StatelessWidget { const DeleteProfileDialog({ @@ -34,7 +34,8 @@ class DeleteProfileDialog extends StatelessWidget { actions: [ TextButton( onPressed: () { - Navigator.of(context).pop(); + // Navigator.of(context).pop(); + Get.back(); }, child: Text( 'Cancel', @@ -48,8 +49,9 @@ class DeleteProfileDialog extends StatelessWidget { ElevatedButton( onPressed: () { try { - ProfilesWidget.of(context).deleteProfile(profile); - Navigator.of(context).pop(); + Get.find().deleteProfile(profile); + // Navigator.of(context).pop(); + Get.back(); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( 'Profile: ${profile.characters} Deleted Successfully', diff --git a/lib/widgets/profilefunctions/manageprofile.dart b/lib/app/modules/profile/views/manageprofile.dart similarity index 91% rename from lib/widgets/profilefunctions/manageprofile.dart rename to lib/app/modules/profile/views/manageprofile.dart index 829ed1c8..5d1f437f 100644 --- a/lib/widgets/profilefunctions/manageprofile.dart +++ b/lib/app/modules/profile/views/manageprofile.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; import 'package:tuple/tuple.dart'; -import 'package:taskwarrior/config/app_settings.dart'; - class ManageProfile extends StatelessWidget { const ManageProfile( this.rename, @@ -49,7 +48,6 @@ class ManageProfile extends StatelessWidget { textColor: AppSettings.isDarkMode ? TaskWarriorColors.white : TaskWarriorColors.black, - key: const PageStorageKey('manage-profile'), title: Text( 'Manage selected profile', style: GoogleFonts.poppins( diff --git a/lib/app/modules/profile/views/profile_view.dart b/lib/app/modules/profile/views/profile_view.dart new file mode 100644 index 00000000..ef1931bb --- /dev/null +++ b/lib/app/modules/profile/views/profile_view.dart @@ -0,0 +1,318 @@ +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:taskwarrior/app/models/storage/savefile.dart'; +import 'package:taskwarrior/app/modules/profile/views/deleteprofiledialog.dart'; +import 'package:taskwarrior/app/modules/profile/views/manageprofile.dart'; +import 'package:taskwarrior/app/modules/profile/views/renameprofiledialog.dart'; +import 'package:taskwarrior/app/modules/profile/views/selectprofile.dart'; +import 'package:taskwarrior/app/routes/app_pages.dart'; +import 'package:taskwarrior/app/utils/constants/palette.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +import '../controllers/profile_controller.dart'; + +class ProfileView extends GetView { + const ProfileView({super.key}); + @override + Widget build(BuildContext context) { + controller.initProfilePageTour(); + controller.showProfilePageTour(context); + return Scaffold( + appBar: AppBar( + backgroundColor: Palette.kToDark.shade200, + title: Obx(() => Text( + controller.profilesMap.length == 1 ? 'Profile' : 'Profiles', + style: GoogleFonts.poppins(color: TaskWarriorColors.white), + )), + leading: IconButton( + onPressed: () { + // Navigator.pushReplacementNamed(context, PageRoutes.home); + // Navigator.of(context).pop(); + Get.back(); + }, + icon: Icon( + Icons.chevron_left, + color: TaskWarriorColors.white, + size: 30, + ), + ), + ), + //primary: false, + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + body: SingleChildScrollView( + child: Column( + children: [ + Obx( + () => ProfilesColumn( + currentProfileKey: controller.currentProfileKey, + addNewProfileKey: controller.addNewProfileKey, + controller.profilesMap, + controller.currentProfile.value, + controller.profilesWidget.addProfile, + controller.profilesWidget.selectProfile, + () => showDialog( + context: context, + builder: (context) => Center( + child: RenameProfileDialog( + profile: controller.currentProfile.value, + alias: controller + .profilesMap[controller.currentProfile.value], + context: context, + ), + ), + ), + // () => Navigator.push( + // context, + // MaterialPageRoute( + // // builder: (_) => const ConfigureTaskserverRoute(), + // builder: (_) => const ManageTaskServerView(), + // ), + // ), + () => Get.toNamed(Routes.MANAGE_TASK_SERVER), + () { + var tasks = controller.profilesWidget + .getStorage(controller.currentProfile.value) + .data + .export(); + var now = DateTime.now() + .toIso8601String() + .replaceAll(RegExp(r'[-:]'), '') + .replaceAll(RegExp(r'\..*'), ''); + + showDialog( + context: context, + builder: (BuildContext context) { + return Utils.showAlertDialog( + title: Text( + "Export Format", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + content: Text( + "Choose the export format:", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + actions: [ + TextButton( + child: Text( + "JSON", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + onPressed: () { + // Navigator.of(context).pop(); + Get.back(); + exportTasks( + contents: tasks, + suggestedName: 'tasks-$now.json', + ); + }, + ), + TextButton( + child: Text( + "TXT", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + onPressed: () { + // Navigator.of(context).pop(); + Get.back(); + exportTasks( + contents: tasks, + suggestedName: 'tasks-$now.txt', + ); + }, + ), + ], + ); + }, + ); + }, + () { + try { + controller.profilesWidget.copyConfigToNewProfile( + controller.currentProfile.value, + ); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'Profile Config Copied', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2))); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'Profile Config Copy Failed', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2))); + } + }, + () => showDialog( + context: context, + builder: (context) => DeleteProfileDialog( + profile: controller.currentProfile.value, + context: context, + ), + ), + ), + ), + ], + ), + ), + ); + } +} + +class ProfilesColumn extends StatelessWidget { + const ProfilesColumn( + this.profilesMap, + this.currentProfile, + this.addProfile, + this.selectProfile, + this.rename, + this.configure, + this.export, + this.copy, + this.delete, { + required this.currentProfileKey, + required this.addNewProfileKey, + super.key, + }); + + final Map profilesMap; + final String currentProfile; + final void Function() addProfile; + final void Function(String) selectProfile; + final void Function() rename; + final void Function() configure; + final void Function() export; + final void Function() copy; + final void Function() delete; + final GlobalKey currentProfileKey; + final GlobalKey addNewProfileKey; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SelectProfile( + currentProfile, + profilesMap, + selectProfile, + currentProfileKey: currentProfileKey, + ), + const SizedBox( + height: 6, + ), + ManageProfile( + rename, + configure, + export, + copy, + delete, + ), + const SizedBox( + height: 6, + ), + ElevatedButton.icon( + onPressed: () { + try { + addProfile(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Profile Added Successfully', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2)), + ); + // Get.find().update(); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Profile Additon Failed', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2), + ), + ); + } + }, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + ), + ), + icon: Icon(Icons.add, + color: AppSettings.isDarkMode + ? TaskWarriorColors.deepPurpleAccent + : TaskWarriorColors.deepPurple), + label: Text( + 'Add new Profile', + key: addNewProfileKey, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/widgets/profilefunctions/renameprofiledialog.dart b/lib/app/modules/profile/views/renameprofiledialog.dart similarity index 74% rename from lib/widgets/profilefunctions/renameprofiledialog.dart rename to lib/app/modules/profile/views/renameprofiledialog.dart index 54d7afee..4a60e4a6 100644 --- a/lib/widgets/profilefunctions/renameprofiledialog.dart +++ b/lib/app/modules/profile/views/renameprofiledialog.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/utility/utilities.dart'; - -import 'package:taskwarrior/widgets/taskdetails.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; class RenameProfileDialog extends StatelessWidget { const RenameProfileDialog({ @@ -36,14 +36,15 @@ class RenameProfileDialog extends StatelessWidget { content: TextField( style: TextStyle( color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, + ? TaskWarriorColors.white + : TaskWarriorColors.black, ), controller: controller), actions: [ TextButton( onPressed: () { - Navigator.of(context).pop(); + // Navigator.of(context).pop(); + Get.back(); }, child: Text( 'Cancel', @@ -56,11 +57,12 @@ class RenameProfileDialog extends StatelessWidget { ), ElevatedButton( onPressed: () { - ProfilesWidget.of(context).renameProfile( + Get.find().renameProfile( profile: profile, alias: controller.text, ); - Navigator.of(context).pop(); + // Navigator.of(context).pop(); + Get.back(); }, child: Text( 'Submit', diff --git a/lib/widgets/profilefunctions/selectprofile.dart b/lib/app/modules/profile/views/selectprofile.dart similarity index 55% rename from lib/widgets/profilefunctions/selectprofile.dart rename to lib/app/modules/profile/views/selectprofile.dart index 1d446e10..c22c4412 100644 --- a/lib/widgets/profilefunctions/selectprofile.dart +++ b/lib/app/modules/profile/views/selectprofile.dart @@ -1,79 +1,51 @@ import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:sizer/sizer.dart'; - -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; class SelectProfile extends StatelessWidget { const SelectProfile( this.currentProfile, this.profilesMap, this.selectProfile, { + required this.currentProfileKey, super.key, }); final String currentProfile; final Map profilesMap; final void Function(String) selectProfile; + final GlobalKey currentProfileKey; @override Widget build(BuildContext context) { - return ExpansionTile( - key: const PageStorageKey('task-list'), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - iconColor: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - collapsedIconColor: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - collapsedTextColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryTextColor - : TaskWarriorColors.kLightSecondaryTextColor, - textColor: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Current Profile:', - overflow: TextOverflow.fade, - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - SizedBox( - height: 1.h, - ), - Text(currentProfile, - style: GoogleFonts.poppins( - fontSize: TaskWarriorFonts.fontSizeSmall, - color: AppSettings.isDarkMode - ? TaskWarriorColors.grey - : TaskWarriorColors.lightGrey, - )) - ], - ), - children: [ - SizedBox( - height: 1.h, - ), - Padding( - padding: EdgeInsets.only(left: 4.w), - child: Row( + return Obx(() => ExpansionTile( + key: const PageStorageKey('task-list'), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + iconColor: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + collapsedIconColor: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + collapsedTextColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryTextColor + : TaskWarriorColors.kLightSecondaryTextColor, + textColor: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'All Profiles:', + 'Current Profile:', + key : currentProfileKey, overflow: TextOverflow.fade, style: GoogleFonts.poppins( fontWeight: TaskWarriorFonts.bold, @@ -83,21 +55,52 @@ class SelectProfile extends StatelessWidget { : TaskWarriorColors.black, ), ), + SizedBox( + height: Get.height * 0.01, + ), + Text(currentProfile, + style: GoogleFonts.poppins( + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.grey + : TaskWarriorColors.lightGrey, + )) ], ), - ), - SizedBox( - height: 1.h, - ), - for (var entry in profilesMap.entries) - SelectProfileListTile( - currentProfile, - entry.key, - () => selectProfile(entry.key), - entry.value, - ) - ], - ); + children: [ + SizedBox( + height: Get.height * 0.01, + ), + Padding( + padding: EdgeInsets.only(left: Get.height * 0.04), + child: Row( + children: [ + Text( + 'All Profiles:', + overflow: TextOverflow.fade, + style: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ), + SizedBox( + height: Get.height * 0.01, + ), + for (var entry in profilesMap.entries) + SelectProfileListTile( + currentProfile, + entry.key, + () => selectProfile(entry.key), + entry.value, + ) + ], + )); } } @@ -127,7 +130,8 @@ class SelectProfileListTile extends StatelessWidget { onChanged: (_) { select(); ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( content: Text( 'Switched to Profile ${alias ?? uuid}', style: TextStyle( @@ -139,7 +143,10 @@ class SelectProfileListTile extends StatelessWidget { backgroundColor: AppSettings.isDarkMode ? TaskWarriorColors.ksecondaryBackgroundColor : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); + duration: const Duration(seconds: 2), + ), + ); + Get.find().refreshTaskWithNewProfile(); }, activeColor: AppSettings.isDarkMode ? TaskWarriorColors.white diff --git a/lib/app/modules/reports/bindings/reports_binding.dart b/lib/app/modules/reports/bindings/reports_binding.dart new file mode 100644 index 00000000..7fe60f9c --- /dev/null +++ b/lib/app/modules/reports/bindings/reports_binding.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; + +import '../controllers/reports_controller.dart'; + +class ReportsBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => ReportsController(), + ); + } +} diff --git a/lib/app/modules/reports/controllers/reports_controller.dart b/lib/app/modules/reports/controllers/reports_controller.dart new file mode 100644 index 00000000..e5544f7c --- /dev/null +++ b/lib/app/modules/reports/controllers/reports_controller.dart @@ -0,0 +1,445 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:taskwarrior/app/models/json/task.dart'; +import 'package:taskwarrior/app/models/storage.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; +import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart'; +import 'package:taskwarrior/app/tour/reports_page_tour.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; + +class ReportsController extends GetxController + with GetTickerProviderStateMixin { + late TabController tabController; + final GlobalKey daily = GlobalKey(); + final GlobalKey weekly = GlobalKey(); + final GlobalKey monthly = GlobalKey(); + + var isSaved = false.obs; + late TutorialCoachMark tutorialCoachMark; + + var selectedIndex = 0.obs; + var allData = [].obs; + late Storage storage; + var storageWidget; + + // void _initReportsTour() { + // tutorialCoachMark = TutorialCoachMark( + // targets: reportsDrawer( + // daily: daily, + // weekly: weekly, + // monthly: monthly, + // ), + // colorShadow: TaskWarriorColors.black, + // paddingFocus: 10, + // opacityShadow: 0.8, + // hideSkip: true, + // onFinish: () { + // SaveReportsTour().saveReportsTourStatus(); + // }, + // ); + // } + + // void showReportsTour() { + // Future.delayed( + // const Duration(seconds: 2), + // () { + // SaveReportsTour().getReportsTourStatus().then((value) => { + // if (value == false) + // { + // tutorialCoachMark.show(context: Get.context!), + // } + // else + // { + // // ignore: avoid_print + // print('User has seen this page'), + // } + // }); + // }, + // ); + // } + + void initReportsTour() { + tutorialCoachMark = TutorialCoachMark( + targets: reportsDrawer( + daily: daily, + weekly: weekly, + monthly: monthly, + ), + colorShadow: TaskWarriorColors.black, + paddingFocus: 10, + opacityShadow: 0.8, + hideSkip: true, + onFinish: () { + SaveTourStatus.saveReportsTourStatus(true); + }, + ); + } + + void showReportsTour(BuildContext context) { + Future.delayed( + const Duration(milliseconds: 500), + () { + SaveTourStatus.getReportsTourStatus().then((value) => { + if (value == false) + { + tutorialCoachMark.show(context: context), + } + else + { + // ignore: avoid_print + print('User has seen this page'), + } + }); + }, + ); + } + + @override + void onInit() { + super.onInit(); + initDailyReports(); + initWeeklyReports(); + initMonthlyReports(); + + tabController = TabController(length: 3, vsync: this); + + Future.delayed(Duration.zero, () { + var currentProfile = Get.find().currentProfile; + Directory baseDirectory = Get.find().baseDirectory(); + storage = + Storage(Directory('${baseDirectory.path}/profiles/$currentProfile')); + + allData.value = storage.data.allData(); + }); + } + + /// This method is used to get the daily burn down data + late TooltipBehavior dailyBurndownTooltipBehaviour; + + ///this method is used to get the weekly burn down data + late TooltipBehavior weeklyBurndownTooltipBehaviour; + + // daily report + + void initDailyReports() { + ///initialize the _dailyBurndownTooltipBehaviour tooltip behavior + dailyBurndownTooltipBehaviour = TooltipBehavior( + enable: true, + builder: (dynamic data, dynamic point, dynamic series, int pointIndex, + int seriesIndex) { + final String date = data.x; + final int pendingCount = data.y1; + final int completedCount = data.y2; + + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Date: $date', + // style: GoogleFonts.poppins( + // fontWeight: TaskWarriorFonts.bold, + // ), + + style: const TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + color: Colors.black), + ), + Text( + 'Pending: $pendingCount', + style: const TextStyle( + fontWeight: TaskWarriorFonts.bold, color: Colors.black), + ), + Text( + 'Completed: $completedCount', + style: const TextStyle( + fontWeight: TaskWarriorFonts.bold, color: Colors.black), + ), + ], + ), + ); + }, + ); + + ///initialize the storage widget + Future.delayed(Duration.zero, () { + storageWidget = Get.find(); + var currentProfile = Get.find().currentProfile; + + Directory baseDirectory = Get.find().baseDirectory(); + storage = Storage( + Directory('${baseDirectory.path}/profiles/$currentProfile'), + ); + + ///fetch all data contains all the tasks + allData.value = storage.data.allData(); + + ///check if allData is not empty + if (allData.isNotEmpty) { + ///sort the data by daily burn down + sortBurnDownDaily(); + } + }); + } + + /// dailyInfo is a map that contains the daily burn down data + /// The key is the date (formatted as "MM-dd") and the value is a map + /// containing the pending and completed tasks count for that day. + RxMap> dailyInfo = >{}.obs; + + void sortBurnDownDaily() { + // Initialize dailyInfo map + dailyInfo.value = {}; + + // Sort allData by entry date in ascending order + allData.sort((a, b) => a.entry.compareTo(b.entry)); + + /// Loop through allData and get the date + for (int i = 0; i < allData.length; i++) { + final String date = Utils.formatDate(allData[i].entry, 'MM-dd'); + + /// Check if dailyInfo contains the date + if (dailyInfo.containsKey(date)) { + /// Check if the status is pending or completed + if (allData[i].status == 'pending') { + /// If the status is pending, then add 1 to the pending count + dailyInfo[date]!['pending'] = (dailyInfo[date]!['pending'] ?? 0) + 1; + } else if (allData[i].status == 'completed') { + /// If the status is completed, then add 1 to the completed count + dailyInfo[date]!['completed'] = + (dailyInfo[date]!['completed'] ?? 0) + 1; + } + } else { + /// If dailyInfo does not contain the date + dailyInfo[date] = { + 'pending': allData[i].status == 'pending' ? 1 : 0, + 'completed': allData[i].status == 'completed' ? 1 : 0, + }; + } + } + + debugPrint("dailyInfo $dailyInfo"); + } + + // weekly reports + + ///weeklyInfo is a map that contains the weekly burn down data + ///first int holds the week value + ///the second map holds the pending and completed tasks + ///the key is the status and the value is the count + RxMap> weeklyInfo = >{}.obs; + + void sortBurnDownWeekLy() { + // Initialize weeklyInfo map + weeklyInfo.value = {}; + + // Sort allData by entry date in ascending order + allData.sort((a, b) => a.entry.compareTo(b.entry)); + + ///loop through allData and get the week number + for (int i = 0; i < allData.length; i++) { + final int weekNumber = Utils.getWeekNumbertoInt(allData[i].entry); + + ///check if weeklyInfo contains the week number + if (weeklyInfo.containsKey(weekNumber)) { + ///check if the status is pending or completed + if (allData[i].status == 'pending') { + ///if the status is pending then add 1 to the pending count + weeklyInfo[weekNumber]!['pending'] = + (weeklyInfo[weekNumber]!['pending'] ?? 0) + 1; + } else if (allData[i].status == 'completed') { + ///if the status is completed then add 1 to the completed count + weeklyInfo[weekNumber]!['completed'] = + (weeklyInfo[weekNumber]!['completed'] ?? 0) + 1; + } + } else { + ///if weeklyInfo does not contain the week number + weeklyInfo[weekNumber] = { + 'pending': allData[i].status == 'pending' ? 1 : 0, + 'completed': allData[i].status == 'completed' ? 1 : 0, + }; + } + } + + debugPrint("weeklyInfo $weeklyInfo"); + } + + void initWeeklyReports() { + ///initialize the _weeklyBurndownTooltipBehaviour tooltip behavior + weeklyBurndownTooltipBehaviour = TooltipBehavior( + enable: true, + builder: (dynamic data, dynamic point, dynamic series, int pointIndex, + int seriesIndex) { + final String weekNumber = data.x; + final int pendingCount = data.y1; + final int completedCount = data.y2; + + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + weekNumber, + style: const TextStyle( + fontWeight: TaskWarriorFonts.bold, color: Colors.black), + ), + Text( + 'Pending: $pendingCount', + style: const TextStyle( + fontWeight: TaskWarriorFonts.bold, color: Colors.black), + ), + Text( + 'Completed: $completedCount', + style: const TextStyle( + fontWeight: TaskWarriorFonts.bold, color: Colors.black), + ), + ], + ), + ); + }, + ); + + ///initialize the storage widget + Future.delayed(Duration.zero, () { + storageWidget = Get.find(); + var currentProfile = Get.find().currentProfile; + + Directory baseDirectory = Get.find().baseDirectory(); + storage = Storage( + Directory('${baseDirectory.path}/profiles/$currentProfile'), + ); + + ///fetch all data contains all the tasks + allData.value = storage.data.allData(); + + ///check if allData is not empty + if (allData.isNotEmpty) { + ///sort the data by weekly burn down + sortBurnDownWeekLy(); + } + }); + } + + // monthly report + late TooltipBehavior monthlyBurndownTooltipBehaviour; + RxMap> monthlyInfo = + >{}.obs; + + void sortBurnDownMonthly() { + monthlyInfo.value = {}; + + allData.sort((a, b) => a.entry.compareTo(b.entry)); + + for (int i = 0; i < allData.length; i++) { + final DateTime entryDate = allData[i].entry; + final String monthYear = + '${Utils.getMonthName(entryDate.month)} ${entryDate.year}'; + + if (monthlyInfo.containsKey(monthYear)) { + if (allData[i].status == 'pending') { + monthlyInfo[monthYear]!['pending'] = + (monthlyInfo[monthYear]!['pending'] ?? 0) + 1; + } else if (allData[i].status == 'completed') { + monthlyInfo[monthYear]!['completed'] = + (monthlyInfo[monthYear]!['completed'] ?? 0) + 1; + } + } else { + monthlyInfo[monthYear] = { + 'pending': allData[i].status == 'pending' ? 1 : 0, + 'completed': allData[i].status == 'completed' ? 1 : 0, + }; + } + } + + debugPrint("monthlyInfo: $monthlyInfo"); + } + + void initMonthlyReports() { + monthlyBurndownTooltipBehaviour = TooltipBehavior( + enable: true, + builder: (dynamic data, dynamic point, dynamic series, int pointIndex, + int seriesIndex) { + final String monthYear = data.x; + final int pendingCount = data.y1; + final int completedCount = data.y2; + + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Month-Year: $monthYear', + style: const TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + color: Colors.black), + ), + Text( + 'Pending: $pendingCount', + style: const TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + color: Colors.black), + ), + Text( + 'Completed: $completedCount', + style: const TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + color: Colors.black), + ), + ], + ), + ); + }, + ); + + ///initialize the storage widget + Future.delayed(Duration.zero, () { + storageWidget = Get.find(); + var currentProfile = Get.find().currentProfile; + + Directory baseDirectory = Get.find().baseDirectory(); + storage = Storage( + Directory('${baseDirectory.path}/profiles/$currentProfile'), + ); + + ///fetch all data contains all the tasks + allData.value = storage.data.allData(); + + ///check if allData is not empty + if (allData.isNotEmpty) { + ///sort the data by weekly burn down + sortBurnDownMonthly(); + } + }); + } +} diff --git a/lib/app/modules/reports/views/burn_down_daily.dart b/lib/app/modules/reports/views/burn_down_daily.dart new file mode 100644 index 00000000..f54fdbaa --- /dev/null +++ b/lib/app/modules/reports/views/burn_down_daily.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:taskwarrior/app/models/chart.dart'; +import 'package:taskwarrior/app/modules/reports/controllers/reports_controller.dart'; +import 'package:taskwarrior/app/modules/reports/views/common_chart_indicator.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class BurnDownDaily extends StatelessWidget { + final ReportsController reportsController; + const BurnDownDaily({super.key, required this.reportsController}); + + @override + Widget build(BuildContext context) { + double height = MediaQuery.of(context).size.height; + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: SizedBox( + height: height * 0.6, + child: Obx( + () => SfCartesianChart( + primaryXAxis: CategoryAxis( + title: AxisTitle( + text: SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageDailyDayMonth, + textStyle: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + color: AppSettings.isDarkMode + ? Colors.white + : Colors.black, + fontSize: TaskWarriorFonts.fontSizeSmall, + ), + ), + ), + primaryYAxis: NumericAxis( + title: AxisTitle( + text: SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsPageTasks, + textStyle: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + color: AppSettings.isDarkMode + ? Colors.white + : Colors.black, + fontSize: TaskWarriorFonts.fontSizeSmall, + ), + ), + ), + tooltipBehavior: + reportsController.dailyBurndownTooltipBehaviour, + series: [ + /// This is the completed tasks + StackedColumnSeries( + groupName: 'Group A', + enableTooltip: true, + color: TaskWarriorColors.green, + dataSource: reportsController.dailyInfo.entries + .map((entry) => ChartData( + entry.key, + entry.value['pending'] ?? 0, + entry.value['completed'] ?? 0, + )) + .toList(), + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y2, + name: 'Completed', + ), + + /// This is the pending tasks + StackedColumnSeries( + groupName: 'Group A', + color: TaskWarriorColors.yellow, + enableTooltip: true, + dataSource: reportsController.dailyInfo.entries + .map((entry) => ChartData( + entry.key, + entry.value['pending'] ?? 0, + entry.value['completed'] ?? 0, + )) + .toList(), + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y1, + name: 'Pending', + ), + ], + ), + )), + ), + CommonChartIndicator( + title: SentenceManager(currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageDailyBurnDownChart, + ), + ], + ); + } +} diff --git a/lib/app/modules/reports/views/burn_down_monthly.dart b/lib/app/modules/reports/views/burn_down_monthly.dart new file mode 100644 index 00000000..09ea1684 --- /dev/null +++ b/lib/app/modules/reports/views/burn_down_monthly.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:taskwarrior/app/models/chart.dart'; +import 'package:taskwarrior/app/modules/reports/controllers/reports_controller.dart'; +import 'package:taskwarrior/app/modules/reports/views/common_chart_indicator.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class BurnDownMonthly extends StatelessWidget { + final ReportsController reportsController; + const BurnDownMonthly({super.key, required this.reportsController}); + + @override + Widget build(BuildContext context) { + var height = Get.height; + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: SizedBox( + height: height * 0.6, + child: SfCartesianChart( + primaryXAxis: CategoryAxis( + title: AxisTitle( + text: SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageMonthlyMonthYear, + textStyle: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: + AppSettings.isDarkMode ? Colors.white : Colors.black, + )), + ), + primaryYAxis: NumericAxis( + title: AxisTitle( + text: SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageTasks, + textStyle: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: + AppSettings.isDarkMode ? Colors.white : Colors.black, + )), + ), + tooltipBehavior: + reportsController.monthlyBurndownTooltipBehaviour, + series: [ + StackedColumnSeries( + groupName: 'Group A', + enableTooltip: true, + color: Colors.green, + dataSource: reportsController.monthlyInfo.entries + .map((entry) => ChartData( + entry.key, + entry.value['pending'] ?? 0, + entry.value['completed'] ?? 0, + )) + .toList(), + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y2, + name: 'Completed', + ), + StackedColumnSeries( + groupName: 'Group A', + color: Colors.yellow, + enableTooltip: true, + dataSource: reportsController.monthlyInfo.entries + .map((entry) => ChartData( + entry.key, + entry.value['pending'] ?? 0, + entry.value['completed'] ?? 0, + )) + .toList(), + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y1, + name: 'Pending', + ), + ], + ), + ), + ), + CommonChartIndicator( + title: SentenceManager(currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageMonthlyBurnDownChart, + ), + ], + ); + } +} diff --git a/lib/app/modules/reports/views/burn_down_weekly.dart b/lib/app/modules/reports/views/burn_down_weekly.dart new file mode 100644 index 00000000..aca89732 --- /dev/null +++ b/lib/app/modules/reports/views/burn_down_weekly.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:taskwarrior/app/models/chart.dart'; +import 'package:taskwarrior/app/modules/reports/controllers/reports_controller.dart'; +import 'package:taskwarrior/app/modules/reports/views/common_chart_indicator.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class BurnDownWeekly extends StatelessWidget { + final ReportsController reportsController; + const BurnDownWeekly({super.key, required this.reportsController}); + + @override + Widget build(BuildContext context) { + var height = Get.height; + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: SizedBox( + height: height * 0.6, + child: SfCartesianChart( + primaryXAxis: CategoryAxis( + title: AxisTitle( + text: SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageWeeklyWeeksYear, + textStyle: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: + AppSettings.isDarkMode ? Colors.white : Colors.black, + )), + ), + primaryYAxis: NumericAxis( + title: AxisTitle( + text: SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsPageTasks, + textStyle: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: + AppSettings.isDarkMode ? Colors.white : Colors.black, + )), + ), + tooltipBehavior: reportsController.weeklyBurndownTooltipBehaviour, + series: [ + ///this is the completed tasks + StackedColumnSeries( + groupName: 'Group A', + enableTooltip: true, + color: TaskWarriorColors.green, + dataSource: reportsController.allData + .map((task) => ChartData( + 'Week ${Utils.getWeekNumbertoInt(task.entry)}, ${task.entry.year}', + reportsController.weeklyInfo[ + Utils.getWeekNumbertoInt(task.entry)] + ?['pending'] ?? + 0, + reportsController.weeklyInfo[ + Utils.getWeekNumbertoInt(task.entry)] + ?['completed'] ?? + 0, + )) + .toList(), + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y2, + name: 'Completed', + ), + + ///this is the pending tasks + StackedColumnSeries( + groupName: 'Group A', + color: TaskWarriorColors.yellow, + enableTooltip: true, + dataSource: reportsController.allData + .map((task) => ChartData( + 'Week ${Utils.getWeekNumbertoInt(task.entry)}, ${task.entry.year}', + reportsController.weeklyInfo[ + Utils.getWeekNumbertoInt(task.entry)] + ?['pending'] ?? + 0, + reportsController.weeklyInfo[ + Utils.getWeekNumbertoInt(task.entry)] + ?['completed'] ?? + 0, + )) + .toList(), + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y1, + name: 'Pending', + ), + ], + ), + ), + ), + CommonChartIndicator( + title: SentenceManager(currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageWeeklyBurnDownChart, + ), + ], + ); + } +} diff --git a/lib/views/reports/widgets/commonChartIndicator.dart b/lib/app/modules/reports/views/common_chart_indicator.dart similarity index 54% rename from lib/views/reports/widgets/commonChartIndicator.dart rename to lib/app/modules/reports/views/common_chart_indicator.dart index 021ba92d..86b90d39 100644 --- a/lib/views/reports/widgets/commonChartIndicator.dart +++ b/lib/app/modules/reports/views/common_chart_indicator.dart @@ -1,12 +1,10 @@ -// ignore_for_file: file_names - import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; -///Common widget for Report chart indicator class CommonChartIndicator extends StatelessWidget { final String title; const CommonChartIndicator({super.key, required this.title}); @@ -20,12 +18,11 @@ class CommonChartIndicator extends StatelessWidget { children: [ Text( title, - style: GoogleFonts.poppins( + style: TextStyle( + fontFamily: FontFamily.poppins, fontWeight: TaskWarriorFonts.bold, + color: AppSettings.isDarkMode ? Colors.white : Colors.black, fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, ), ) ], @@ -44,13 +41,14 @@ class CommonChartIndicator extends StatelessWidget { decoration: BoxDecoration(color: TaskWarriorColors.green), ), Text( - "Completed", - style: GoogleFonts.poppins( + SentenceManager(currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageCompleted, + style: TextStyle( + fontFamily: FontFamily.poppins, fontWeight: TaskWarriorFonts.regular, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, + color: AppSettings.isDarkMode ? Colors.white : Colors.black, + fontSize: TaskWarriorFonts.fontSizeSmall, ), ), ], @@ -63,13 +61,14 @@ class CommonChartIndicator extends StatelessWidget { decoration: BoxDecoration(color: TaskWarriorColors.yellow), ), Text( - "Pending", - style: GoogleFonts.poppins( + SentenceManager(currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPagePending, + style: TextStyle( + fontFamily: FontFamily.poppins, fontWeight: TaskWarriorFonts.regular, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, + color: AppSettings.isDarkMode ? Colors.white : Colors.black, + fontSize: TaskWarriorFonts.fontSizeSmall, ), ), ], diff --git a/lib/app/modules/reports/views/reports_view.dart b/lib/app/modules/reports/views/reports_view.dart new file mode 100644 index 00000000..a2ceb546 --- /dev/null +++ b/lib/app/modules/reports/views/reports_view.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:taskwarrior/app/modules/reports/views/burn_down_daily.dart'; +import 'package:taskwarrior/app/modules/reports/views/burn_down_monthly.dart'; +import 'package:taskwarrior/app/modules/reports/views/burn_down_weekly.dart'; +import 'package:taskwarrior/app/utils/constants/constants.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +import '../controllers/reports_controller.dart'; + +class ReportsView extends GetView { + const ReportsView({super.key}); + + @override + Widget build(BuildContext context) { + controller.initReportsTour(); + controller.showReportsTour(context); + double height = MediaQuery.of(context).size.height; + + return Scaffold( + appBar: AppBar( + backgroundColor: TaskWarriorColors.kprimaryBackgroundColor, + title: Text( + SentenceManager(currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageTitle, + style: GoogleFonts.poppins(color: TaskWarriorColors.white), + ), + leading: GestureDetector( + onTap: () { + Get.back(); + }, + child: Icon( + Icons.chevron_left, + color: TaskWarriorColors.white, + ), + ), + bottom: PreferredSize( + preferredSize: Size.fromHeight(height * 0.1), + child: TabBar( + controller: controller.tabController, + labelColor: TaskWarriorColors.white, + labelStyle: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.medium, + fontSize: TaskWarriorFonts.fontSizeSmall, + ), + unselectedLabelStyle: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.light, + ), + onTap: (value) { + controller.selectedIndex.value = value; + }, + tabs: [ + Tab( + key: controller.daily, + icon: const Icon(Icons.schedule), + text: SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageDaily, + iconMargin: const EdgeInsets.only(bottom: 0.0), + ), + Tab( + key: controller.weekly, + icon: const Icon(Icons.today), + text: SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageWeekly, + iconMargin: const EdgeInsets.only(bottom: 0.0), + ), + Tab( + key: controller.monthly, + icon: const Icon(Icons.date_range), + text: SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageMonthly, + iconMargin: const EdgeInsets.only(bottom: 0.0), + ), + ], + ), + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.white, + body: Obx( + () => controller.allData.isEmpty + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.heart_broken, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageNoTasksFound, + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.medium, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageAddTasksToSeeReports, + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.light, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ], + ) + : IndexedStack( + index: controller.selectedIndex.value, + children: [ + BurnDownDaily( + reportsController: controller, + ), + BurnDownWeekly( + reportsController: controller, + ), + BurnDownMonthly( + reportsController: controller, + ), + ], + ), + ), + ); + } +} diff --git a/lib/app/modules/settings/bindings/settings_binding.dart b/lib/app/modules/settings/bindings/settings_binding.dart new file mode 100644 index 00000000..9efb3069 --- /dev/null +++ b/lib/app/modules/settings/bindings/settings_binding.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; + +import '../controllers/settings_controller.dart'; + +class SettingsBinding extends Bindings { + @override + void dependencies() { + Get.put( + SettingsController(), + ); + } +} diff --git a/lib/app/modules/settings/controllers/settings_controller.dart b/lib/app/modules/settings/controllers/settings_controller.dart new file mode 100644 index 00000000..9e08f687 --- /dev/null +++ b/lib/app/modules/settings/controllers/settings_controller.dart @@ -0,0 +1,184 @@ +import 'dart:io'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; +import 'package:taskwarrior/app/utils/language/supported_language.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; + +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; +import 'package:path/path.dart' as path; + +import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart'; + +class SettingsController extends GetxController { + RxBool isMovingDirectory = false.obs; + + Rx selectedLanguage = AppSettings.selectedLanguage.obs; + RxString baseDirectory = "".obs; + + void setSelectedLanguage(SupportedLanguage language) async { + await SelectedLanguage.saveSelectedLanguage(language); + selectedLanguage.value = language; + AppSettings.selectedLanguage = language; + Get.find().selectedLanguage.value = language; + } + + Future getBaseDirectory() async { + SplashController profilesWidget = Get.find(); + Directory baseDir = profilesWidget.baseDirectory(); + Directory defaultDirectory = await profilesWidget.getDefaultDirectory(); + if (baseDir.path == defaultDirectory.path) { + return 'Default'; + } else { + return baseDir.path; + } + } + + void pickDirectory(BuildContext context) { + FilePicker.platform.getDirectoryPath().then((value) async { + if (value != null) { + isMovingDirectory.value = true; + update(); + // InheritedProfiles profilesWidget = ProfilesWidget.of(context); + var profilesWidget = Get.find(); + Directory source = profilesWidget.baseDirectory(); + Directory destination = Directory(value); + moveDirectory(source.path, destination.path).then((value) async { + isMovingDirectory.value = false; + update(); + if (value == "same") { + return; + } else if (value == "success") { + profilesWidget.setBaseDirectory(destination); + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString('baseDirectory', destination.path); + } else { + showDialog( + context: context, + builder: (BuildContext context) { + return Utils.showAlertDialog( + title: Text( + 'Error', + style: GoogleFonts.poppins( + fontWeight: FontWeight.bold, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + content: Text( + value == "nested" + ? "Cannot move to a nested directory" + : value == "not-empty" + ? "Destination directory is not empty" + : "An error occurred", + style: GoogleFonts.poppins( + color: TaskWarriorColors.grey, + fontSize: TaskWarriorFonts.fontSizeSmall, + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text( + 'OK', + style: GoogleFonts.poppins( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ) + ], + ); + }, + ); + } + }); + } + }); + } + + Future moveDirectory(String fromDirectory, String toDirectory) async { + if (path.canonicalize(fromDirectory) == path.canonicalize(toDirectory)) { + return "same"; + } + + if (path.isWithin(fromDirectory, toDirectory)) { + return "nested"; + } + + Directory toDir = Directory(toDirectory); + final length = await toDir.list().length; + if (length > 0) { + return "not-empty"; + } + + await moveDirectoryRecurse(fromDirectory, toDirectory); + return "success"; + } + + Future moveDirectoryRecurse( + String fromDirectory, String toDirectory) async { + Directory fromDir = Directory(fromDirectory); + Directory toDir = Directory(toDirectory); + + // Create the toDirectory if it doesn't exist + await toDir.create(recursive: true); + + // Loop through each file and directory and move it to the toDirectory + await for (final entity in fromDir.list()) { + if (entity is File) { + // If it's a file, move it to the toDirectory + File file = entity; + String newPath = path.join( + toDirectory, path.relative(file.path, from: fromDirectory)); + await File(newPath).writeAsBytes(await file.readAsBytes()); + await file.delete(); + } else if (entity is Directory) { + // If it's a directory, create it in the toDirectory and recursively move its contents + Directory dir = entity; + String newPath = path.join( + toDirectory, path.relative(dir.path, from: fromDirectory)); + Directory newDir = Directory(newPath); + await newDir.create(recursive: true); + await moveDirectoryRecurse(dir.path, newPath); + await dir.delete(); + } + } + } + + RxBool isSyncOnStartActivel = false.obs; + RxBool isSyncOnTaskCreateActivel = false.obs; + RxBool delaytask = false.obs; + RxBool change24hr = false.obs; + RxBool isDarkModeOn = false.obs; + + void initDarkMode() { + isDarkModeOn.value = AppSettings.isDarkMode; + } + + @override + void onInit() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + isSyncOnStartActivel.value = prefs.getBool('sync-onStart') ?? false; + isSyncOnTaskCreateActivel.value = + prefs.getBool('sync-OnTaskCreate') ?? false; + delaytask.value = prefs.getBool('delaytask') ?? false; + change24hr.value = prefs.getBool('24hourformate') ?? false; + initDarkMode(); + baseDirectory.value = await getBaseDirectory(); + super.onInit(); + } +} diff --git a/lib/app/modules/settings/views/settings_page_app_bar.dart b/lib/app/modules/settings/views/settings_page_app_bar.dart new file mode 100644 index 00000000..a65fd51d --- /dev/null +++ b/lib/app/modules/settings/views/settings_page_app_bar.dart @@ -0,0 +1,73 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; + +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; + +import '../controllers/settings_controller.dart'; + +import 'package:taskwarrior/app/utils/constants/palette.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; + +class SettingsPageAppBar extends StatelessWidget + implements PreferredSizeWidget { + final SettingsController controller; + const SettingsPageAppBar({required this.controller, super.key}); + + @override + Widget build(BuildContext context) { + return AppBar( + centerTitle: false, + backgroundColor: Palette.kToDark.shade200, + title: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Obx( + () => Text( + SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageTitle, + style: GoogleFonts.poppins( + color: TaskWarriorColors.white, + fontSize: TaskWarriorFonts.fontSizeLarge, + ), + ), + ), + Obx( + () => Text( + SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageSubtitle, + style: GoogleFonts.poppins( + color: TaskWarriorColors.white, + fontSize: TaskWarriorFonts.fontSizeSmall, + ), + ), + ), + ], + ), + leading: GestureDetector( + onTap: () { + // Get.toNamed(Routes.b) + // ; + Get.back(); + }, + child: Icon( + Icons.chevron_left, + color: TaskWarriorColors.white, + ), + ), + ); + } + + @override + Size get preferredSize => AppBar().preferredSize; +} diff --git a/lib/app/modules/settings/views/settings_page_body.dart b/lib/app/modules/settings/views/settings_page_body.dart new file mode 100644 index 00000000..c69bbe0f --- /dev/null +++ b/lib/app/modules/settings/views/settings_page_body.dart @@ -0,0 +1,137 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import 'package:google_fonts/google_fonts.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; + +import '../controllers/settings_controller.dart'; + +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; + +import 'package:taskwarrior/app/modules/settings/views/settings_page_enable_24hr_format_list_tile_trailing.dart'; +import 'package:taskwarrior/app/modules/settings/views/settings_page_highlist_task_list_tile_trailing.dart'; +import 'package:taskwarrior/app/modules/settings/views/settings_page_list_tile.dart'; +import 'package:taskwarrior/app/modules/settings/views/settings_page_on_task_create_list_tile_trailing.dart'; +import 'package:taskwarrior/app/modules/settings/views/settings_page_on_task_start_list_tile_trailing.dart'; +import 'package:taskwarrior/app/modules/settings/views/settings_page_select_directory_list_tile.dart'; +import 'package:taskwarrior/app/modules/settings/views/settings_page_select_the_language_trailing.dart'; + +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class SettingsPageBody extends StatelessWidget { + final SettingsController controller; + + const SettingsPageBody({required this.controller, super.key}); + + @override + Widget build(BuildContext context) { + return Obx(() { + if (controller.isMovingDirectory.value) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 10), + Text( + SentenceManager( + currentLanguage: controller.selectedLanguage.value, + ).sentences.settingsPageMovingDataToNewDirectory, + style: GoogleFonts.poppins( + fontWeight: FontWeight.bold, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ); + } else { + return ListView( + children: [ + Obx( + () => SettingsPageListTile( + title: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageSyncOnStartTitle, + subTitle: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageSyncOnStartDescription, + trailing: SettingsPageOnTaskStartListTileTrailing( + controller: controller, + ), + ), + ), + const Divider(), + Obx( + () => SettingsPageListTile( + title: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageEnableSyncOnTaskCreateTitle, + subTitle: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageEnableSyncOnTaskCreateDescription, + trailing: SettingsPageOnTaskCreateListTileTrailing( + controller: controller, + ), + ), + ), + const Divider(), + SettingsPageListTile( + title: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageHighlightTaskTitle, + subTitle: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageHighlightTaskDescription, + trailing: SettingsPageHighlistTaskListTileTrailing( + controller: controller, + ), + ), + const Divider(), + SettingsPageSelectDirectoryListTile(controller: controller), + const Divider(), + SettingsPageListTile( + title: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageEnable24hrFormatTitle, + subTitle: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageEnable24hrFormatDescription, + trailing: SettingsPageEnable24hrFormatListTileTrailing( + controller: controller, + ), + ), + const Divider(), + SettingsPageListTile( + title: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageSelectLanguage, + subTitle: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageToggleNativeLanguage, + trailing: SettingsPageSelectTheLanguageTrailing( + controller: controller, + ), + ), + ], + ); + } + }); + } +} diff --git a/lib/app/modules/settings/views/settings_page_enable_24hr_format_list_tile_trailing.dart b/lib/app/modules/settings/views/settings_page_enable_24hr_format_list_tile_trailing.dart new file mode 100644 index 00000000..19b44a58 --- /dev/null +++ b/lib/app/modules/settings/views/settings_page_enable_24hr_format_list_tile_trailing.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; + +import '../controllers/settings_controller.dart'; + + +class SettingsPageEnable24hrFormatListTileTrailing extends StatelessWidget { + final SettingsController controller; + const SettingsPageEnable24hrFormatListTileTrailing( + {required this.controller, super.key}); + + @override + Widget build(BuildContext context) { + return Obx( + () => Switch( + value: controller.change24hr.value, + onChanged: (bool value) async { + controller.change24hr.value = value; + + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setBool('24hourformate', value); + Get.find().change24hr.value = value; + }, + ), + ); + } +} diff --git a/lib/app/modules/settings/views/settings_page_highlist_task_list_tile_trailing.dart b/lib/app/modules/settings/views/settings_page_highlist_task_list_tile_trailing.dart new file mode 100644 index 00000000..013e3fd6 --- /dev/null +++ b/lib/app/modules/settings/views/settings_page_highlist_task_list_tile_trailing.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; + +import '../controllers/settings_controller.dart'; + + +class SettingsPageHighlistTaskListTileTrailing extends StatelessWidget { + final SettingsController controller; + const SettingsPageHighlistTaskListTileTrailing( + {required this.controller, super.key}); + + @override + Widget build(BuildContext context) { + return Obx( + () => Switch( + value: controller.delaytask.value, + onChanged: (bool value) async { + controller.delaytask.value = value; + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setBool('delaytask', value); + Get.find().useDelayTask.value = value; + }, + ), + ); + } +} diff --git a/lib/app/modules/settings/views/settings_page_list_tile.dart b/lib/app/modules/settings/views/settings_page_list_tile.dart new file mode 100644 index 00000000..a8f11f37 --- /dev/null +++ b/lib/app/modules/settings/views/settings_page_list_tile.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + + +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; + + +class SettingsPageListTile extends StatelessWidget { + final String title; + final String subTitle; + final Widget? trailing; + + const SettingsPageListTile({ + super.key, + required this.title, + required this.subTitle, + this.trailing, + }); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text( + title, + style: GoogleFonts.poppins( + fontWeight: FontWeight.bold, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + subtitle: Text( + subTitle, + style: GoogleFonts.poppins( + color: TaskWarriorColors.grey, + fontSize: TaskWarriorFonts.fontSizeSmall, + ), + ), + trailing: trailing, + ); + } +} diff --git a/lib/app/modules/settings/views/settings_page_on_task_create_list_tile_trailing.dart b/lib/app/modules/settings/views/settings_page_on_task_create_list_tile_trailing.dart new file mode 100644 index 00000000..7b166f79 --- /dev/null +++ b/lib/app/modules/settings/views/settings_page_on_task_create_list_tile_trailing.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../controllers/settings_controller.dart'; + + +class SettingsPageOnTaskCreateListTileTrailing extends StatelessWidget { + final SettingsController controller; + const SettingsPageOnTaskCreateListTileTrailing( + {required this.controller, super.key}); + + @override + Widget build(BuildContext context) { + return Obx( + () => Switch( + value: controller.isSyncOnTaskCreateActivel.value, + onChanged: (bool value) async { + controller.isSyncOnTaskCreateActivel.value = value; + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setBool('sync-OnTaskCreate', value); + }, + ), + ); + } +} diff --git a/lib/app/modules/settings/views/settings_page_on_task_start_list_tile_trailing.dart b/lib/app/modules/settings/views/settings_page_on_task_start_list_tile_trailing.dart new file mode 100644 index 00000000..80fc423b --- /dev/null +++ b/lib/app/modules/settings/views/settings_page_on_task_start_list_tile_trailing.dart @@ -0,0 +1,29 @@ + +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../controllers/settings_controller.dart'; + + +class SettingsPageOnTaskStartListTileTrailing extends StatelessWidget { + final SettingsController controller; + const SettingsPageOnTaskStartListTileTrailing({required this.controller, super.key}); + + @override + Widget build(BuildContext context) { + return Obx( + () => Switch( + value: controller.isSyncOnStartActivel.value, + onChanged: (bool value) async { + controller.isSyncOnStartActivel.value = value; + + final SharedPreferences prefs = + await SharedPreferences.getInstance(); + await prefs.setBool('sync-onStart', value); + }, + ), + ); + } +} diff --git a/lib/app/modules/settings/views/settings_page_select_directory_list_tile.dart b/lib/app/modules/settings/views/settings_page_select_directory_list_tile.dart new file mode 100644 index 00000000..d11712f5 --- /dev/null +++ b/lib/app/modules/settings/views/settings_page_select_directory_list_tile.dart @@ -0,0 +1,197 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +import '../controllers/settings_controller.dart'; + +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; + +class SettingsPageSelectDirectoryListTile extends StatelessWidget { + final SettingsController controller; + const SettingsPageSelectDirectoryListTile( + {required this.controller, super.key}); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text( + SentenceManager(currentLanguage: AppSettings.selectedLanguage) + .sentences + .settingsPageSelectDirectoryTitle, + style: GoogleFonts.poppins( + fontWeight: FontWeight.bold, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + subtitle: Column( + children: [ + Obx( + () => Text( + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.settingsPageSelectDirectoryTitle}: ${controller.baseDirectory.value}', + style: GoogleFonts.poppins( + color: TaskWarriorColors.grey, + fontSize: TaskWarriorFonts.fontSizeSmall, + ), + ), + ), + const SizedBox( + height: 10, + ), + Row( + children: [ + //Reset to default + TextButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + ), + ), + onPressed: () async { + if (await controller.getBaseDirectory() == "Default") { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'Already default', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2))); + } else { + showDialog( + context: context, + builder: (BuildContext context) { + return Utils.showAlertDialog( + title: Text( + 'Reset to default', + style: GoogleFonts.poppins( + fontWeight: FontWeight.bold, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + content: Text( + "Are you sure you want to reset the directory to the default?", + style: GoogleFonts.poppins( + color: TaskWarriorColors.grey, + fontSize: TaskWarriorFonts.fontSizeMedium, + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text( + 'No', + style: GoogleFonts.poppins( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ), + TextButton( + onPressed: () async { + Navigator.pop(context); + controller.isMovingDirectory.value = true; + + // InheritedProfiles profilesWidget = + // ProfilesWidget.of(context); + var profilesWidget = + Get.find(); + + Directory source = + profilesWidget.baseDirectory(); + Directory destination = + await profilesWidget.getDefaultDirectory(); + controller + .moveDirectory( + source.path, destination.path) + .then((value) async { + profilesWidget.setBaseDirectory(destination); + SharedPreferences prefs = + await SharedPreferences.getInstance(); + await prefs.remove('baseDirectory'); + controller.isMovingDirectory.value = false; + }); + }, + child: Text( + 'Yes', + style: GoogleFonts.poppins( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ), + ], + ); + }, + ); + } + }, + child: Text( + SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageSetToDefault, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.deepPurple, + ), + ), + ), + const Spacer(), + //Change directory + TextButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + ), + ), + onPressed: () { + controller.pickDirectory(context); + }, + child: Text( + SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageChangeDirectory, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.deepPurple, + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/app/modules/settings/views/settings_page_select_the_language_trailing.dart b/lib/app/modules/settings/views/settings_page_select_the_language_trailing.dart new file mode 100644 index 00000000..29f59527 --- /dev/null +++ b/lib/app/modules/settings/views/settings_page_select_the_language_trailing.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/settings/controllers/settings_controller.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/language/supported_language.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class SettingsPageSelectTheLanguageTrailing extends StatelessWidget { + final SettingsController controller; + + const SettingsPageSelectTheLanguageTrailing( + {required this.controller, super.key}); + + @override + Widget build(BuildContext context) { + return Obx( + () => DropdownButton( + value: controller.selectedLanguage.value, + onChanged: (value) { + controller.setSelectedLanguage(value!); + }, + items: SupportedLanguage.values.map((language) { + return DropdownMenuItem( + value: language, + child: Text( + _getLanguageName(language), + style: TextStyle( + fontFamily: FontFamily.poppins, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ); + }).toList(), + dropdownColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor + ), + ); + } + + String _getLanguageName(SupportedLanguage language) { + switch (language) { + case SupportedLanguage.english: + return 'English'; + case SupportedLanguage.hindi: + return 'Hindi'; + case SupportedLanguage.marathi: + return 'Marathi'; + default: + return ''; + } + } +} diff --git a/lib/app/modules/settings/views/settings_view.dart b/lib/app/modules/settings/views/settings_view.dart new file mode 100644 index 00000000..68922554 --- /dev/null +++ b/lib/app/modules/settings/views/settings_view.dart @@ -0,0 +1,29 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/settings/views/settings_page_app_bar.dart'; +import 'package:taskwarrior/app/modules/settings/views/settings_page_body.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +import '../controllers/settings_controller.dart'; + +class SettingsView extends GetView { + const SettingsView({super.key}); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: SettingsPageAppBar( + controller: controller, + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + body: SettingsPageBody( + controller: controller, + ), + ); + } +} diff --git a/lib/app/modules/splash/bindings/splash_binding.dart b/lib/app/modules/splash/bindings/splash_binding.dart new file mode 100644 index 00000000..fcb9b7ca --- /dev/null +++ b/lib/app/modules/splash/bindings/splash_binding.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; + +import '../controllers/splash_controller.dart'; + +class SplashBinding extends Bindings { + @override + void dependencies() { + Get.put( + SplashController(), + ); + } +} diff --git a/lib/app/modules/splash/controllers/splash_controller.dart b/lib/app/modules/splash/controllers/splash_controller.dart new file mode 100644 index 00000000..05d7d5a0 --- /dev/null +++ b/lib/app/modules/splash/controllers/splash_controller.dart @@ -0,0 +1,103 @@ +import 'dart:io'; + +import 'package:get/get.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:taskwarrior/app/models/storage.dart'; +import 'package:taskwarrior/app/routes/app_pages.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/profiles.dart'; + +class SplashController extends GetxController { + late Rx baseDirectory = Directory('').obs; + late RxMap profilesMap = {}.obs; + late RxString currentProfile = ''.obs; + + Profiles get _profiles => Profiles(baseDirectory.value); + + @override + void onInit() async { + super.onInit(); + initBaseDir().then((_) { + _checkProfiles(); + profilesMap.value = _profiles.profilesMap(); + currentProfile.value = _profiles.getCurrentProfile()!; + sendToNextPage(); + }); + } + + Future initBaseDir() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? directory = prefs.getString('baseDirectory'); + Directory dir = (directory != null) + ? Directory(directory) + : await getDefaultDirectory(); + baseDirectory.value = dir; + } + + void _checkProfiles() { + if (_profiles.profilesMap().isEmpty) { + _profiles.setCurrentProfile(_profiles.addProfile()); + } else if (!_profiles + .profilesMap() + .containsKey(_profiles.getCurrentProfile())) { + _profiles.setCurrentProfile(_profiles.profilesMap().keys.first); + } + } + + Future getDefaultDirectory() async { + return await getApplicationDocumentsDirectory(); + } + + void setBaseDirectory(Directory newBaseDirectory) { + baseDirectory.value = newBaseDirectory; + profilesMap.value = _profiles.profilesMap(); + } + + void addProfile() { + _profiles.addProfile(); + profilesMap.value = _profiles.profilesMap(); + } + + void copyConfigToNewProfile(String profile) { + _profiles.copyConfigToNewProfile(profile); + profilesMap.value = _profiles.profilesMap(); + } + + void deleteProfile(String profile) { + _profiles.deleteProfile(profile); + _checkProfiles(); + profilesMap.value = _profiles.profilesMap(); + currentProfile.value = _profiles.getCurrentProfile()!; + } + + void renameProfile({required String profile, required String? alias}) { + _profiles.setAlias(profile: profile, alias: alias!); + profilesMap.value = _profiles.profilesMap(); + } + + void selectProfile(String profile) { + _profiles.setCurrentProfile(profile); + currentProfile.value = _profiles.getCurrentProfile()!; + } + + Storage getStorage(String profile) { + return _profiles.getStorage(profile); + } + + RxBool hasCompletedOnboarding = false.obs; + + Future checkOnboardingStatus() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + hasCompletedOnboarding.value = + prefs.getBool('onboarding_completed') ?? false; + } + + void sendToNextPage() async { + await checkOnboardingStatus(); + if (hasCompletedOnboarding.value) { + Get.toNamed(Routes.HOME); + } else { + Get.toNamed(Routes.ONBOARDING); + } + } +} diff --git a/lib/app/modules/splash/views/splash_view.dart b/lib/app/modules/splash/views/splash_view.dart new file mode 100644 index 00000000..d34a7494 --- /dev/null +++ b/lib/app/modules/splash/views/splash_view.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import 'package:get/get.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/gen/assets.gen.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; + +import '../controllers/splash_controller.dart'; + +class SplashView extends GetView { + const SplashView({super.key}); + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + child: SvgPicture.asset( + Assets.svg.logo.path, + height: 100, + width: double.infinity, + )), + const SizedBox(height: 30.0), + const CircularProgressIndicator(), + const SizedBox(height: 16.0), + const Text( + "Setting up the app...", + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeLarge, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart new file mode 100644 index 00000000..ec084a3e --- /dev/null +++ b/lib/app/routes/app_pages.dart @@ -0,0 +1,77 @@ +import 'package:get/get.dart'; + +import '../modules/about/bindings/about_binding.dart'; +import '../modules/about/views/about_view.dart'; + +import '../modules/detailRoute/bindings/detail_route_binding.dart'; +import '../modules/detailRoute/views/detail_route_view.dart'; +import '../modules/home/bindings/home_binding.dart'; +import '../modules/home/views/home_view.dart'; +import '../modules/manageTaskServer/bindings/manage_task_server_binding.dart'; +import '../modules/manageTaskServer/views/manage_task_server_view.dart'; +import '../modules/onboarding/bindings/onboarding_binding.dart'; +import '../modules/onboarding/views/onboarding_view.dart'; +import '../modules/profile/bindings/profile_binding.dart'; +import '../modules/profile/views/profile_view.dart'; +import '../modules/reports/bindings/reports_binding.dart'; +import '../modules/reports/views/reports_view.dart'; +import '../modules/settings/bindings/settings_binding.dart'; +import '../modules/settings/views/settings_view.dart'; +import '../modules/splash/bindings/splash_binding.dart'; +import '../modules/splash/views/splash_view.dart'; + +part 'app_routes.dart'; + +class AppPages { + AppPages._(); + + static const INITIAL = Routes.SPLASH; + + static final routes = [ + GetPage( + name: _Paths.HOME, + page: () => const HomeView(), + binding: HomeBinding(), + ), + GetPage( + name: _Paths.ONBOARDING, + page: () => const OnboardingView(), + binding: OnboardingBinding(), + ), + GetPage( + name: _Paths.SPLASH, + page: () => const SplashView(), + binding: SplashBinding(), + ), + GetPage( + name: _Paths.MANAGE_TASK_SERVER, + page: () => const ManageTaskServerView(), + binding: ManageTaskServerBinding(), + ), + GetPage( + name: _Paths.DETAIL_ROUTE, + page: () => const DetailRouteView(), + binding: DetailRouteBinding(), + ), + GetPage( + name: _Paths.PROFILE, + page: () => const ProfileView(), + binding: ProfileBinding(), + ), + GetPage( + name: _Paths.ABOUT, + page: () => const AboutView(), + binding: AboutBinding(), + ), + GetPage( + name: _Paths.REPORTS, + page: () => const ReportsView(), + binding: ReportsBinding(), + ), + GetPage( + name: _Paths.SETTINGS, + page: () => const SettingsView(), + binding: SettingsBinding(), + ), + ]; +} diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart new file mode 100644 index 00000000..5a1af88c --- /dev/null +++ b/lib/app/routes/app_routes.dart @@ -0,0 +1,28 @@ +part of 'app_pages.dart'; +// DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart + +abstract class Routes { + Routes._(); + static const HOME = _Paths.HOME; + static const ONBOARDING = _Paths.ONBOARDING; + static const SPLASH = _Paths.SPLASH; + static const MANAGE_TASK_SERVER = _Paths.MANAGE_TASK_SERVER; + static const DETAIL_ROUTE = _Paths.DETAIL_ROUTE; + static const PROFILE = _Paths.PROFILE; + static const ABOUT = _Paths.ABOUT; + static const REPORTS = _Paths.REPORTS; + static const SETTINGS = _Paths.SETTINGS; +} + +abstract class _Paths { + _Paths._(); + static const HOME = '/home'; + static const ONBOARDING = '/onboarding'; + static const SPLASH = '/splash'; + static const MANAGE_TASK_SERVER = '/manage-task-server'; + static const DETAIL_ROUTE = '/detail-route'; + static const PROFILE = '/profile'; + static const ABOUT = '/about'; + static const REPORTS = '/reports'; + static const SETTINGS = '/settings'; +} diff --git a/lib/services/notification_services.dart b/lib/app/services/notification_services.dart similarity index 100% rename from lib/services/notification_services.dart rename to lib/app/services/notification_services.dart diff --git a/lib/services/pushNotification_service.dart b/lib/app/services/pushNotification_service.dart similarity index 100% rename from lib/services/pushNotification_service.dart rename to lib/app/services/pushNotification_service.dart diff --git a/lib/app/services/tag_filter.dart b/lib/app/services/tag_filter.dart new file mode 100644 index 00000000..2eaa7a56 --- /dev/null +++ b/lib/app/services/tag_filter.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; + + +class TagFilterMetadata { + const TagFilterMetadata({ + required this.display, + required this.selected, + }); + + final String display; + final bool selected; +} + +class TagFilters { + const TagFilters({ + required this.tagUnion, + required this.toggleTagUnion, + required this.tags, + required this.toggleTagFilter, + }); + + final bool tagUnion; + final void Function() toggleTagUnion; + final Map tags; + final void Function(String) toggleTagFilter; +} + +class TagFiltersWrap extends StatelessWidget { + const TagFiltersWrap(this.filters, {super.key}); + + final TagFilters filters; + + @override + Widget build(BuildContext context) { + return Wrap( + spacing: 4, + children: [ + FilterChip( + onSelected: (_) => filters.toggleTagUnion(), + label: Text( + filters.tagUnion ? 'OR' : 'AND', + // style: GoogleFonts.poppins( + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.black + // : TaskWarriorColors.white) + ), + // backgroundColor: AppSettings.isDarkMode + // ? TaskWarriorColors.kLightSecondaryBackgroundColor + // : TaskWarriorColors.ksecondaryBackgroundColor, + ), + for (var entry in filters.tags.entries) + FilterChip( + onSelected: (_) => filters.toggleTagFilter(entry.key), + label: Text( + entry.value.display, + // style: GoogleFonts.poppins( + // fontWeight: + // entry.value.selected ? TaskWarriorFonts.bold : null, + // color: AppSettings.isDarkMode + // ? TaskWarriorColors.black + // : TaskWarriorColors.white), + ), + // backgroundColor: AppSettings.isDarkMode + // ? TaskWarriorColors.kLightSecondaryBackgroundColor + // : TaskWarriorColors.kprimaryBackgroundColor, + ), + ], + ); + } +} diff --git a/lib/app/themes/dark_theme.dart b/lib/app/themes/dark_theme.dart new file mode 100644 index 00000000..91bcf93b --- /dev/null +++ b/lib/app/themes/dark_theme.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart'; + +ThemeData darkTheme = ThemeData( + brightness: Brightness.dark, +); \ No newline at end of file diff --git a/lib/app/themes/light_theme.dart b/lib/app/themes/light_theme.dart new file mode 100644 index 00000000..604edfad --- /dev/null +++ b/lib/app/themes/light_theme.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart'; + +ThemeData lightTheme = ThemeData( + brightness: Brightness.light, +); diff --git a/lib/app/themes/themes.dart b/lib/app/themes/themes.dart new file mode 100644 index 00000000..a72656ca --- /dev/null +++ b/lib/app/themes/themes.dart @@ -0,0 +1,2 @@ +export 'dark_theme.dart'; +export 'light_theme.dart'; \ No newline at end of file diff --git a/lib/drawer/filter_drawer_tour.dart b/lib/app/tour/filter_drawer_tour.dart similarity index 98% rename from lib/drawer/filter_drawer_tour.dart rename to lib/app/tour/filter_drawer_tour.dart index b2bd7e78..67eb4655 100644 --- a/lib/drawer/filter_drawer_tour.dart +++ b/lib/app/tour/filter_drawer_tour.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; List filterDrawer({ diff --git a/lib/views/home/home_tour.dart b/lib/app/tour/home_page_tour.dart similarity index 100% rename from lib/views/home/home_tour.dart rename to lib/app/tour/home_page_tour.dart diff --git a/lib/app/tour/profile_page_tour.dart b/lib/app/tour/profile_page_tour.dart new file mode 100644 index 00000000..8ea6a9b2 --- /dev/null +++ b/lib/app/tour/profile_page_tour.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; + +List addProfilePage({ + required GlobalKey currentProfileKey, + + required GlobalKey addNewProfileKey, +}) { + List targets = []; + + // currentProfile + targets.add( + TargetFocus( + keyTarget: currentProfileKey, + alignSkip: Alignment.topRight, + radius: 10, + shape: ShapeLightFocus.Circle, + contents: [ + TargetContent( + align: ContentAlign.top, + builder: (context, controller) { + return Container( + alignment: Alignment.center, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "See your current profile here", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + color: Colors.white, + ), + ), + ], + ), + ); + }, + ), + ], + ), + ); + + + //addNewProfile + targets.add( + TargetFocus( + keyTarget: addNewProfileKey, + alignSkip: Alignment.topRight, + radius: 10, + shape: ShapeLightFocus.Circle, + contents: [ + TargetContent( + align: ContentAlign.bottom, + builder: (context, controller) { + return Container( + alignment: Alignment.center, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Add a new profile here", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + color: Colors.white, + ), + ), + ], + ), + ); + }, + ), + ], + ), + ); + + return targets; +} diff --git a/lib/views/reports/reports_tour.dart b/lib/app/tour/reports_page_tour.dart similarity index 100% rename from lib/views/reports/reports_tour.dart rename to lib/app/tour/reports_page_tour.dart diff --git a/lib/app/utils/constants/constants.dart b/lib/app/utils/constants/constants.dart new file mode 100644 index 00000000..3a8aed22 --- /dev/null +++ b/lib/app/utils/constants/constants.dart @@ -0,0 +1,4 @@ +export './onboarding_screen_content.dart'; +export './taskwarrior_fonts.dart'; +export './palette.dart'; +export './taskwarrior_colors.dart'; \ No newline at end of file diff --git a/lib/views/Onboarding/Model/onboarding_contents.dart b/lib/app/utils/constants/onboarding_screen_content.dart similarity index 69% rename from lib/views/Onboarding/Model/onboarding_contents.dart rename to lib/app/utils/constants/onboarding_screen_content.dart index 76cdfc5f..a0d2dd3a 100644 --- a/lib/views/Onboarding/Model/onboarding_contents.dart +++ b/lib/app/utils/constants/onboarding_screen_content.dart @@ -1,38 +1,25 @@ import 'package:flutter/material.dart'; - -class OnboardingModel { - final String title; - final String image; - final String desc; - final Color colors; - - OnboardingModel({ - required this.title, - required this.image, - required this.desc, - required this.colors, - }); -} +import 'package:taskwarrior/app/models/onboarding_model.dart'; +import 'package:taskwarrior/app/utils/gen/assets.gen.dart'; List contents = [ OnboardingModel( title: "Welcome to Taskwarrior", - image: "assets/svg/s1.svg", + image:Assets.svg.s1.path, colors: const Color(0xffDAD3C8), desc: "Welcome to Taskwarrior, your ultimate task management solution. Manage your tasks, group them, and organize your work with ease.", ), OnboardingModel( title: "Powerful Reporting", - image: "assets/svg/s3.svg", + image:Assets.svg.s2.path, colors: const Color(0xffFFE5DE), desc: "Generate insightful reports to analyze your task data. Gain valuable insights into your productivity and make data-driven decisions.", ), OnboardingModel( title: "Sync Across Devices", - // image: "assets/svg/s3.svg", - image: "assets/svg/s2.svg", + image:Assets.svg.s3.path, colors: const Color(0xffDCF6E6), desc: "Sync your tasks seamlessly across multiple Taskwarrior clients, ensuring you're always up to date and have access to your reports on any device.", diff --git a/lib/widgets/pallete.dart b/lib/app/utils/constants/palette.dart similarity index 100% rename from lib/widgets/pallete.dart rename to lib/app/utils/constants/palette.dart diff --git a/lib/config/taskwarriorcolors.dart b/lib/app/utils/constants/taskwarrior_colors.dart similarity index 95% rename from lib/config/taskwarriorcolors.dart rename to lib/app/utils/constants/taskwarrior_colors.dart index 4c5134ef..d9417ed3 100644 --- a/lib/config/taskwarriorcolors.dart +++ b/lib/app/utils/constants/taskwarrior_colors.dart @@ -1,7 +1,7 @@ // ignore_for_file: file_names import 'package:flutter/material.dart'; -import 'package:taskwarrior/widgets/pallete.dart'; +import 'package:taskwarrior/app/utils/constants/palette.dart'; class TaskWarriorColors { // Normal Colors diff --git a/lib/config/taskwarriorfonts.dart b/lib/app/utils/constants/taskwarrior_fonts.dart similarity index 100% rename from lib/config/taskwarriorfonts.dart rename to lib/app/utils/constants/taskwarrior_fonts.dart diff --git a/lib/utility/utilities.dart b/lib/app/utils/constants/utilites.dart similarity index 82% rename from lib/utility/utilities.dart rename to lib/app/utils/constants/utilites.dart index 5fe07669..764f4304 100644 --- a/lib/utility/utilities.dart +++ b/lib/app/utils/constants/utilites.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; class Utils { static String getWeekNumber(DateTime? date) { @@ -81,15 +81,6 @@ class Utils { bool scrollable = false, }) { return AlertDialog( - surfaceTintColor: AppSettings.isDarkMode - ? TaskWarriorColors.kdialogBackGroundColor - : TaskWarriorColors.kLightDialogBackGroundColor, - shadowColor: AppSettings.isDarkMode - ? TaskWarriorColors.kdialogBackGroundColor - : TaskWarriorColors.kLightDialogBackGroundColor, - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kdialogBackGroundColor - : TaskWarriorColors.kLightDialogBackGroundColor, key: key, title: title, titlePadding: titlePadding, @@ -110,6 +101,7 @@ class Utils { shape: shape, alignment: alignment, scrollable: scrollable, + backgroundColor:AppSettings.isDarkMode? TaskWarriorColors.kdialogBackGroundColor : TaskWarriorColors.kLightDialogBackGroundColor, ); } } diff --git a/lib/app/utils/gen/assets.gen.dart b/lib/app/utils/gen/assets.gen.dart new file mode 100644 index 00000000..39452eaa --- /dev/null +++ b/lib/app/utils/gen/assets.gen.dart @@ -0,0 +1,186 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use + +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter/services.dart'; + +class $AssetsImagesGen { + const $AssetsImagesGen(); + + /// File path: assets/images/taskwarrior.png + AssetGenImage get taskwarrior => + const AssetGenImage('assets/images/taskwarrior.png'); + + /// File path: assets/images/taskwarrior_dark.png + AssetGenImage get taskwarriorDark => + const AssetGenImage('assets/images/taskwarrior_dark.png'); + + /// List of all assets + List get values => [taskwarrior, taskwarriorDark]; +} + +class $AssetsSvgGen { + const $AssetsSvgGen(); + + /// File path: assets/svg/github.svg + SvgGenImage get github => const SvgGenImage('assets/svg/github.svg'); + + /// File path: assets/svg/link.svg + SvgGenImage get link => const SvgGenImage('assets/svg/link.svg'); + + /// File path: assets/svg/logo.svg + SvgGenImage get logo => const SvgGenImage('assets/svg/logo.svg'); + + /// File path: assets/svg/s1.svg + SvgGenImage get s1 => const SvgGenImage('assets/svg/s1.svg'); + + /// File path: assets/svg/s2.svg + SvgGenImage get s2 => const SvgGenImage('assets/svg/s2.svg'); + + /// File path: assets/svg/s3.svg + SvgGenImage get s3 => const SvgGenImage('assets/svg/s3.svg'); + + /// List of all assets + List get values => [github, link, logo, s1, s2, s3]; +} + +class Assets { + Assets._(); + + static const $AssetsImagesGen images = $AssetsImagesGen(); + static const $AssetsSvgGen svg = $AssetsSvgGen(); +} + +class AssetGenImage { + const AssetGenImage(this._assetName); + + final String _assetName; + + Image image({ + Key? key, + AssetBundle? bundle, + ImageFrameBuilder? frameBuilder, + ImageErrorWidgetBuilder? errorBuilder, + String? semanticLabel, + bool excludeFromSemantics = false, + double? scale, + double? width, + double? height, + Color? color, + Animation? opacity, + BlendMode? colorBlendMode, + BoxFit? fit, + AlignmentGeometry alignment = Alignment.center, + ImageRepeat repeat = ImageRepeat.noRepeat, + Rect? centerSlice, + bool matchTextDirection = false, + bool gaplessPlayback = false, + bool isAntiAlias = false, + String? package, + FilterQuality filterQuality = FilterQuality.low, + int? cacheWidth, + int? cacheHeight, + }) { + return Image.asset( + _assetName, + key: key, + bundle: bundle, + frameBuilder: frameBuilder, + errorBuilder: errorBuilder, + semanticLabel: semanticLabel, + excludeFromSemantics: excludeFromSemantics, + scale: scale, + width: width, + height: height, + color: color, + opacity: opacity, + colorBlendMode: colorBlendMode, + fit: fit, + alignment: alignment, + repeat: repeat, + centerSlice: centerSlice, + matchTextDirection: matchTextDirection, + gaplessPlayback: gaplessPlayback, + isAntiAlias: isAntiAlias, + package: package, + filterQuality: filterQuality, + cacheWidth: cacheWidth, + cacheHeight: cacheHeight, + ); + } + + ImageProvider provider({ + AssetBundle? bundle, + String? package, + }) { + return AssetImage( + _assetName, + bundle: bundle, + package: package, + ); + } + + String get path => _assetName; + + String get keyName => _assetName; +} + +class SvgGenImage { + const SvgGenImage(this._assetName); + + final String _assetName; + + SvgPicture svg({ + Key? key, + bool matchTextDirection = false, + AssetBundle? bundle, + String? package, + double? width, + double? height, + BoxFit fit = BoxFit.contain, + AlignmentGeometry alignment = Alignment.center, + bool allowDrawingOutsideViewBox = false, + WidgetBuilder? placeholderBuilder, + String? semanticsLabel, + bool excludeFromSemantics = false, + SvgTheme theme = const SvgTheme(), + ColorFilter? colorFilter, + Clip clipBehavior = Clip.hardEdge, + @deprecated Color? color, + @deprecated BlendMode colorBlendMode = BlendMode.srcIn, + @deprecated bool cacheColorFilter = false, + }) { + return SvgPicture.asset( + _assetName, + key: key, + matchTextDirection: matchTextDirection, + bundle: bundle, + package: package, + width: width, + height: height, + fit: fit, + alignment: alignment, + allowDrawingOutsideViewBox: allowDrawingOutsideViewBox, + placeholderBuilder: placeholderBuilder, + semanticsLabel: semanticsLabel, + excludeFromSemantics: excludeFromSemantics, + theme: theme, + colorFilter: colorFilter, + color: color, + colorBlendMode: colorBlendMode, + clipBehavior: clipBehavior, + cacheColorFilter: cacheColorFilter, + ); + } + + String get path => _assetName; + + String get keyName => _assetName; +} diff --git a/lib/app/utils/gen/fonts.gen.dart b/lib/app/utils/gen/fonts.gen.dart new file mode 100644 index 00000000..4da254ad --- /dev/null +++ b/lib/app/utils/gen/fonts.gen.dart @@ -0,0 +1,24 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use + +class FontFamily { + FontFamily._(); + + /// Font family: Poppins + static const String poppins = 'Poppins'; + + /// Font family: SFProDisplay + static const String sFProDisplay = 'SFProDisplay'; + + /// Font family: SegoeUI + static const String segoeUI = 'SegoeUI'; + + /// Font family: Ubuntu + static const String ubuntu = 'Ubuntu'; +} diff --git a/lib/app/utils/home_path/home_path.dart b/lib/app/utils/home_path/home_path.dart new file mode 100644 index 00000000..3ad5f6f0 --- /dev/null +++ b/lib/app/utils/home_path/home_path.dart @@ -0,0 +1,5 @@ +export './impl/data.dart'; +export './impl/taskd_client.dart'; +export './impl/home.dart'; +export './impl/gui_pem_file_paths.dart'; +export './impl/taskrc.dart'; \ No newline at end of file diff --git a/lib/widgets/home_paths/impl/data.dart b/lib/app/utils/home_path/impl/data.dart similarity index 96% rename from lib/widgets/home_paths/impl/data.dart rename to lib/app/utils/home_path/impl/data.dart index 30fb2ced..d118f01b 100644 --- a/lib/widgets/home_paths/impl/data.dart +++ b/lib/app/utils/home_path/impl/data.dart @@ -4,9 +4,10 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:io'; -import 'package:taskwarrior/model/json.dart'; -import 'package:taskwarrior/widgets/taskc/payload.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; +import 'package:taskwarrior/app/models/json/task.dart'; +import 'package:taskwarrior/app/utils/taskc/payload.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/urgency.dart'; + class Data { Data(this.home); diff --git a/lib/widgets/home_paths/impl/gui_pem_file_paths.dart b/lib/app/utils/home_path/impl/gui_pem_file_paths.dart similarity index 96% rename from lib/widgets/home_paths/impl/gui_pem_file_paths.dart rename to lib/app/utils/home_path/impl/gui_pem_file_paths.dart index 3295038e..9805e177 100644 --- a/lib/widgets/home_paths/impl/gui_pem_file_paths.dart +++ b/lib/app/utils/home_path/impl/gui_pem_file_paths.dart @@ -1,6 +1,8 @@ import 'dart:io'; -import 'package:taskwarrior/widgets/taskserver.dart'; +import 'package:taskwarrior/app/utils/taskserver/pem_file_paths.dart'; + + class GUIPemFiles { GUIPemFiles(this.home); diff --git a/lib/widgets/home_paths/home.dart b/lib/app/utils/home_path/impl/home.dart similarity index 78% rename from lib/widgets/home_paths/home.dart rename to lib/app/utils/home_path/impl/home.dart index d46ea386..3fd0c9cf 100644 --- a/lib/widgets/home_paths/home.dart +++ b/lib/app/utils/home_path/impl/home.dart @@ -1,8 +1,9 @@ import 'dart:io'; -import 'package:taskwarrior/model/storage/exceptions/bad_certificate_exception.dart'; -import 'package:taskwarrior/widgets/home_paths.dart'; -import 'package:taskwarrior/widgets/taskserver.dart' as rc; +import 'package:taskwarrior/app/models/data.dart'; +import 'package:taskwarrior/app/models/storage/exceptions/bad_certificate_exception.dart'; +import 'package:taskwarrior/app/utils/home_path/impl/taskd_client.dart'; +import 'package:taskwarrior/app/utils/taskserver/taskserver.dart' as rc; class Home { const Home({ diff --git a/lib/widgets/home_paths/impl/taskd_client.dart b/lib/app/utils/home_path/impl/taskd_client.dart similarity index 85% rename from lib/widgets/home_paths/impl/taskd_client.dart rename to lib/app/utils/home_path/impl/taskd_client.dart index e300bb01..8d6c0f40 100644 --- a/lib/widgets/home_paths/impl/taskd_client.dart +++ b/lib/app/utils/home_path/impl/taskd_client.dart @@ -3,12 +3,16 @@ import 'dart:async'; import 'dart:io'; -import 'package:taskwarrior/model/storage/exceptions/taskserver_configuration_exception.dart'; -import 'package:taskwarrior/widgets/taskc/impl/codec.dart'; -import 'package:taskwarrior/widgets/taskc/impl/message.dart'; -import 'package:taskwarrior/widgets/taskc/message.dart'; -import 'package:taskwarrior/widgets/taskc/response.dart'; -import 'package:taskwarrior/widgets/taskserver.dart'; + + +import 'package:taskwarrior/app/models/storage/exceptions/taskserver_configuration_exception.dart'; +import 'package:taskwarrior/app/utils/taskc/impl/codec.dart'; +import 'package:taskwarrior/app/utils/taskc/impl/message.dart'; +import 'package:taskwarrior/app/utils/taskc/message.dart'; +import 'package:taskwarrior/app/utils/taskc/response.dart'; +import 'package:taskwarrior/app/utils/taskserver/pem_file_paths.dart'; +import 'package:taskwarrior/app/utils/taskserver/taskrc.dart'; + enum TaskserverProgress { connecting, diff --git a/lib/widgets/home_paths/impl/taskrc.dart b/lib/app/utils/home_path/impl/taskrc.dart similarity index 100% rename from lib/widgets/home_paths/impl/taskrc.dart rename to lib/app/utils/home_path/impl/taskrc.dart diff --git a/lib/app/utils/language/english_sentences.dart b/lib/app/utils/language/english_sentences.dart new file mode 100644 index 00000000..e4334550 --- /dev/null +++ b/lib/app/utils/language/english_sentences.dart @@ -0,0 +1,190 @@ +import 'package:taskwarrior/app/utils/language/sentences.dart'; + +class EnglishSentences extends Sentences { + @override + String get helloWorld => 'Hello, World!'; + + @override + String get homePageTitle => 'Home Page'; + @override + String get homePageLastModified => 'Last Modified'; + @override + String get homePageDue => 'Due'; + @override + String get homePageTaskWarriorNotConfigured => 'TaskServer is not configured'; + @override + String get homePageSetup => 'Setup'; + @override + String get homePageFilter => 'Filter'; + @override + String get homePageMenu => 'Menu'; + @override + String get homePageExitApp => 'Exit App'; + @override + String get homePageAreYouSureYouWantToExit => + 'Are you sure you want to exit?'; + @override + String get homePageExit => 'Exit'; + @override + String get homePageCancel => 'Cancel'; + @override + String get homePageClickOnTheBottomRightButtonToStartAddingTasks => + 'Click on the bottom right button to start adding tasks'; + @override + String get homePageSearchNotFound => 'Search Not Found'; + + @override + String get settingsPageTitle => 'Settings Page'; + @override + String get settingsPageSubtitle => 'Configure your preferences'; + @override + String get settingsPageMovingDataToNewDirectory => + 'Moving data to new directory'; + @override + String get settingsPageSyncOnStartTitle => 'Sync on Start'; + @override + String get settingsPageSyncOnStartDescription => + 'Automatically sync data on app start'; + @override + String get settingsPageEnableSyncOnTaskCreateTitle => 'Sync on task create'; + @override + String get settingsPageEnableSyncOnTaskCreateDescription => + 'Enable automatic syncing when creating a new task'; + @override + String get settingsPageHighlightTaskTitle => 'Highlight the task'; + @override + String get settingsPageHighlightTaskDescription => + 'Make the border of task if only 1 day left'; + @override + String get settingsPageEnable24hrFormatTitle => 'Enable 24 hr format'; + @override + String get settingsPageEnable24hrFormatDescription => + 'Switch right to enable 24 hr format'; + @override + String get settingsPageSelectLanguage => 'Select the language'; + @override + String get settingsPageToggleNativeLanguage => + 'Toggle between your native language'; + @override + String get settingsPageSelectDirectoryTitle => 'Select the directory'; + @override + String get settingsPageSelectDirectoryDescription => + 'Select the directory where the TaskWarrior data is stored\nCurrent directory: '; + @override + String get settingsPageChangeDirectory => 'Change Directory'; + @override + String get settingsPageSetToDefault => 'Set To Default'; + + @override + String get navDrawerProfile => 'Profile'; + @override + String get navDrawerReports => 'Reports'; + @override + String get navDrawerAbout => 'About'; + @override + String get navDrawerSettings => 'Settings'; + @override + String get navDrawerExit => 'Exit'; + + @override + String get detailPageDescription => 'Description'; + @override + String get detailPageStatus => 'Status'; + @override + String get detailPageEntry => 'Entry'; + @override + String get detailPageModified => 'Modified'; + @override + String get detailPageStart => 'Start'; + @override + String get detailPageEnd => 'End'; + @override + String get detailPageDue => 'Due'; + @override + String get detailPageWait => 'Wait'; + @override + String get detailPageUntil => 'Until'; + @override + String get detailPagePriority => 'Priority'; + @override + String get detailPageProject => 'Project'; + @override + String get detailPageTags => 'Tags'; + @override + String get detailPageUrgency => 'Urgency'; + @override + String get detailPageID => 'ID'; + + @override + String get filterDrawerApplyFilters => 'Apply Filters'; + @override + String get filterDrawerHideWaiting => 'Hide Waiting'; + @override + String get filterDrawerShowWaiting => 'Show Waiting'; + @override + String get filterDrawerPending => 'Pending'; + @override + String get filterDrawerCompleted => 'Completed'; + @override + String get filterDrawerFilterTagBy => 'Filter Tag By'; + @override + String get filterDrawerAND => 'AND'; + @override + String get filterDrawerOR => 'OR'; + @override + String get filterDrawerSortBy => 'Sort By'; + @override + String get filterDrawerCreated => 'Created'; + @override + String get filterDrawerModified => 'Modified'; + @override + String get filterDrawerStartTime => 'Start Time'; + @override + String get filterDrawerDueTill => 'Due till'; + @override + String get filterDrawerPriority => 'Priority'; + @override + String get filterDrawerProject => 'Project'; + @override + String get filterDrawerTags => 'Tags'; + @override + String get filterDrawerUrgency => 'Urgency'; + @override + String get filterDrawerResetSort => 'Reset Sort'; + @override + String get filterDrawerStatus => 'Status'; + @override + String get reportsPageTitle => 'Reports'; + @override + String get reportsPageCompleted => 'Completed'; + @override + String get reportsPagePending => 'Pending'; + @override + String get reportsPageTasks => 'Tasks'; + + @override + String get reportsPageDaily => 'Daily'; + @override + String get reportsPageDailyBurnDownChart => 'Daily Burn Down Chart'; + @override + String get reportsPageDailyDayMonth => 'Day - Month'; + + @override + String get reportsPageWeekly => 'Weekly'; + @override + String get reportsPageWeeklyBurnDownChart => 'Weekly Burn Down Chart'; + @override + String get reportsPageWeeklyWeeksYear => 'Weeks - Year'; + + @override + String get reportsPageMonthly => 'Monthly'; + @override + String get reportsPageMonthlyBurnDownChart => 'Monthly Burn Down Chart'; + @override + String get reportsPageMonthlyMonthYear => 'Month - Year'; + + @override + String get reportsPageNoTasksFound => 'No Tasks Found'; + @override + String get reportsPageAddTasksToSeeReports => 'Add Tasks To See Reports'; +} diff --git a/lib/app/utils/language/hindi_sentences.dart b/lib/app/utils/language/hindi_sentences.dart new file mode 100644 index 00000000..d60f036a --- /dev/null +++ b/lib/app/utils/language/hindi_sentences.dart @@ -0,0 +1,191 @@ +import 'package:taskwarrior/app/utils/language/sentences.dart'; + +class HindiSentences extends Sentences { + @override + String get helloWorld => 'नमस्ते दुनिया!'; + @override + String get homePageTitle => 'होम पेज'; + @override + String get homePageLastModified => 'अंतिम बार संशोधित'; + @override + String get homePageDue => 'देय'; + @override + String get homePageTaskWarriorNotConfigured => 'TaskServer कॉन्फ़िगर नहीं है'; + @override + String get homePageSetup => 'सेटअप'; + @override + String get homePageFilter => 'फ़िल्टर'; + @override + String get homePageMenu => 'मेन्यू'; + @override + String get homePageExitApp => 'ऐप बंद करें'; + @override + String get homePageAreYouSureYouWantToExit => + 'क्या आप वाकई ऐप बंद करना चाहते हैं?'; + @override + String get homePageExit => 'बाहर जाओ'; + @override + String get homePageCancel => 'रद्द करें'; + @override + String get homePageClickOnTheBottomRightButtonToStartAddingTasks => + 'कार्यों को जोड़ना शुरू करने के लिए नीचे दाएं बटन पर क्लिक करें'; + @override + String get homePageSearchNotFound => 'खोजने पर नहीं मिला'; + @override + String get settingsPageTitle => 'सेटिंग्स पेज'; + @override + String get settingsPageSubtitle => 'अपनी पसंद सेट करें'; + @override + String get settingsPageMovingDataToNewDirectory => + 'नए निर्देशिका में डेटा को ले जा रहा है'; + @override + String get settingsPageSyncOnStartTitle => + 'ऐप स्टार्ट पर डेटा स्वचालित सिंक करें'; + @override + String get settingsPageSyncOnStartDescription => 'स्टार्ट पर सिंक करें'; + @override + String get settingsPageEnableSyncOnTaskCreateTitle => + 'नई टास्क बनाते समय स्वचालित सिंकिंग सक्षम करें'; + @override + String get settingsPageEnableSyncOnTaskCreateDescription => + 'नई टास्क बनाते समय स्वचालित सिंकिंग सक्षम करें'; + @override + String get settingsPageHighlightTaskTitle => + 'केवल 1 दिन शेष होने पर कार्य की सीमा बनाएं'; + @override + String get settingsPageHighlightTaskDescription => + 'केवल 1 दिन शेष होने पर कार्य की सीमा बनाएं'; + @override + String get settingsPageEnable24hrFormatTitle => + '24 घंटे का प्रारूप सक्षम करें'; + @override + String get settingsPageEnable24hrFormatDescription => + '24 घंटे का प्रारूप सक्षम करें'; + @override + String get settingsPageSelectLanguage => 'भाषा चुनें'; + @override + String get settingsPageToggleNativeLanguage => + 'अपनी मातृभाषा के बीच टॉगल करें'; + @override + String get settingsPageSelectDirectoryTitle => 'निर्देशिका चुनें'; + @override + String get settingsPageSelectDirectoryDescription => + 'निर्देशिका चुनें जहां TaskWarrior डेटा स्टोर होता है\nवर्तमान निर्देशिका: '; + @override + String get settingsPageChangeDirectory => 'निर्देशिका बदलें'; + @override + String get settingsPageSetToDefault => 'डिफॉल्ट पर सेट करें'; + @override + String get navDrawerProfile => 'प्रोफ़ाइल'; + @override + String get navDrawerReports => 'रिपोर्ट्स'; + @override + String get navDrawerAbout => 'के बारे में'; + @override + String get navDrawerSettings => 'सेटिंग्स'; + @override + String get navDrawerExit => 'बाहर जाओ'; + + @override + String get detailPageDescription => 'विवरण'; + @override + String get detailPageStatus => 'स्थिति'; + @override + String get detailPageEntry => 'प्रवेश'; + @override + String get detailPageModified => 'संशोधित'; + @override + String get detailPageStart => 'प्रारंभ'; + @override + String get detailPageEnd => 'अंत'; + @override + String get detailPageDue => 'देय'; + @override + String get detailPageWait => 'प्रतीक्षा करें'; + @override + String get detailPageUntil => 'तक'; + @override + String get detailPagePriority => 'प्राथमिकता'; + @override + String get detailPageProject => 'परियोजना'; + @override + String get detailPageTags => 'टैग'; + @override + String get detailPageUrgency => 'तत्कालता'; + @override + String get detailPageID => 'आयडी'; + + @override + String get filterDrawerApplyFilters => 'फिल्टर लागू करें'; + @override + String get filterDrawerHideWaiting => 'इंतजार छिपाएं'; + @override + String get filterDrawerShowWaiting => 'इंतजार दिखाएं'; + @override + String get filterDrawerPending => 'अपूर्ण'; + @override + String get filterDrawerCompleted => 'पूर्ण'; + @override + String get filterDrawerFilterTagBy => 'टैग से फ़िल्टर करें'; + @override + String get filterDrawerAND => 'और'; + @override + String get filterDrawerOR => 'या'; + @override + String get filterDrawerSortBy => 'इसके आधार पर क्रमबद्ध करें'; + @override + String get filterDrawerCreated => 'निर्मित'; + @override + String get filterDrawerModified => 'संशोधित'; + @override + String get filterDrawerStartTime => 'शुरुआत समय'; + @override + String get filterDrawerDueTill => 'तक बकाया'; + @override + String get filterDrawerPriority => 'प्राथमिकता'; + @override + String get filterDrawerProject => 'परियोजना'; + @override + String get filterDrawerTags => 'टैग्स'; + @override + String get filterDrawerUrgency => 'तत्कालता'; + @override + String get filterDrawerResetSort => 'सॉर्ट रीसेट करें'; + @override + String get filterDrawerStatus => 'स्थिती'; + @override + String get reportsPageTitle => 'रिपोर्ट्स'; + @override + String get reportsPageCompleted => 'पूर्ण'; + @override + String get reportsPagePending => 'अपूर्ण'; + @override + String get reportsPageTasks => 'कार्य'; + + @override + String get reportsPageDaily => 'दैनिक'; + @override + String get reportsPageDailyBurnDownChart => 'दैनिक बर्न डाउन चार्ट'; + @override + String get reportsPageDailyDayMonth => 'दिन - माह'; + + @override + String get reportsPageWeekly => 'साप्ताहिक'; + @override + String get reportsPageWeeklyBurnDownChart => 'साप्ताहिक बर्न डाउन चार्ट'; + @override + String get reportsPageWeeklyWeeksYear => 'सप्ताह - वर्ष'; + + @override + String get reportsPageMonthly => 'मासिक'; + @override + String get reportsPageMonthlyBurnDownChart => 'मासिक बर्न डाउन चार्ट'; + @override + String get reportsPageMonthlyMonthYear => 'माह - वर्ष'; + + @override + String get reportsPageNoTasksFound => 'कोई कार्य नहीं मिला'; + @override + String get reportsPageAddTasksToSeeReports => + 'रिपोर्ट देखने के लिए कार्य जोड़ें'; +} diff --git a/lib/app/utils/language/marathi_sentences.dart b/lib/app/utils/language/marathi_sentences.dart new file mode 100644 index 00000000..41b9d53f --- /dev/null +++ b/lib/app/utils/language/marathi_sentences.dart @@ -0,0 +1,190 @@ +import 'package:taskwarrior/app/utils/language/sentences.dart'; + +class MarathiSentences extends Sentences { + @override + String get helloWorld => 'नमस्कार, जग!'; + @override + String get homePageTitle => 'होम पेज'; + @override + String get homePageLastModified => 'शेवटचा बदल'; + @override + String get homePageDue => 'द्यावे'; + @override + String get homePageTaskWarriorNotConfigured => 'TaskServer संरचीत नाही'; + @override + String get homePageSetup => 'सेटअप'; + @override + String get homePageFilter => 'फिल्टर'; + @override + String get homePageMenu => 'मेनू'; + @override + String get homePageExitApp => 'अ‍ॅप बंद करा'; + @override + String get homePageAreYouSureYouWantToExit => + 'आपण खात्री आहात की आपण अ‍ॅप बंद करू इच्छिता?'; + @override + String get homePageExit => 'बाहेर पडा'; + @override + String get homePageCancel => 'रद्द करा'; + @override + String get homePageClickOnTheBottomRightButtonToStartAddingTasks => + 'कार्ये जोडणे सुरू करण्यासाठी तळाशी उजव्या बटणावर क्लिक करा'; + @override + String get homePageSearchNotFound => 'शोध सापडला नाही'; + @override + String get settingsPageTitle => 'सेटिंग्स पेज'; + @override + String get settingsPageSubtitle => 'तुमची पसंती सेट करा'; + @override + String get settingsPageMovingDataToNewDirectory => + 'नवीन निर्देशिकेत डेटा हलवत आहे'; + @override + String get settingsPageSyncOnStartTitle => 'सुरू करण्यावर सिंक करा'; + @override + String get settingsPageSyncOnStartDescription => + 'अ‍ॅप सुरू करताना डेटा स्वयंसिंक करा'; + @override + String get settingsPageEnableSyncOnTaskCreateTitle => + 'नवीन कार्य तयार करताना स्वयंसिंकिंग सक्षम करा'; + @override + String get settingsPageEnableSyncOnTaskCreateDescription => + 'नवीन कार्य तयार करताना स्वयंसिंकिंग सक्षम करा'; + @override + String get settingsPageHighlightTaskTitle => + 'फक्त 1 दिवस शेष असताना कार्याची सीमा बनवा'; + @override + String get settingsPageHighlightTaskDescription => + 'फक्त 1 दिवस शेष असताना कार्याची सीमा बनवा'; + @override + String get settingsPageEnable24hrFormatTitle => '24 तासाचा स्वरूप सक्षम करा'; + @override + String get settingsPageEnable24hrFormatDescription => + '24 तासाचा स्वरूप सक्षम करा'; + @override + String get settingsPageSelectLanguage => 'भाषा निवडा'; + @override + String get settingsPageToggleNativeLanguage => + 'तुमच्या मूल भाषेतर्फे टॉगल करा'; + @override + String get settingsPageSelectDirectoryTitle => 'निर्देशिका निवडा'; + @override + String get settingsPageSelectDirectoryDescription => + 'निर्देशिका निवडा जिथे TaskWarrior डेटा स्टोर केला जातो\nवर्तमान निर्देशिका: '; + @override + String get settingsPageChangeDirectory => 'डिरेक्टरी बदला'; + @override + String get settingsPageSetToDefault => 'डीफॉल्टवर सेट करा'; + @override + String get navDrawerProfile => 'प्रोफ़ाइल'; + @override + String get navDrawerReports => 'अहवाल'; + @override + String get navDrawerAbout => 'चरित्र'; + @override + String get navDrawerSettings => 'सेटिंग्स'; + @override + String get navDrawerExit => 'बाहेर पडा'; + + @override + String get detailPageDescription => 'वर्णन'; + @override + String get detailPageStatus => 'स्थिती'; + @override + String get detailPageEntry => 'प्रवेश'; + @override + String get detailPageModified => 'संशोधित'; + @override + String get detailPageStart => 'सुरूवात'; + @override + String get detailPageEnd => 'शेवट'; + @override + String get detailPageDue => 'देय'; + @override + String get detailPageWait => 'प्रतीक्षा'; + @override + String get detailPageUntil => 'पर्यंत'; + @override + String get detailPagePriority => 'प्राधान्य'; + @override + String get detailPageProject => 'प्रकल्प'; + @override + String get detailPageTags => 'टॅग'; + @override + String get detailPageUrgency => 'तातडी'; + @override + String get detailPageID => 'आयडी'; + + @override + String get filterDrawerApplyFilters => 'फिल्टर लागू करा'; + @override + String get filterDrawerHideWaiting => 'वाट लपवा'; + @override + String get filterDrawerShowWaiting => 'वाट दाखवा'; + @override + String get filterDrawerPending => 'प्रलंबित'; + @override + String get filterDrawerCompleted => 'पूर्ण'; + @override + String get filterDrawerFilterTagBy => 'टॅगवर फिल्टर करा'; + @override + String get filterDrawerAND => 'आणि'; + @override + String get filterDrawerOR => 'किंवा'; + @override + String get filterDrawerSortBy => 'यानुसार क्रमबद्ध करा'; + @override + String get filterDrawerCreated => 'सृष्ट'; + @override + String get filterDrawerModified => 'संशोधित'; + @override + String get filterDrawerStartTime => 'सुरूवातीचा वेळ'; + @override + String get filterDrawerDueTill => 'पर्यंतचा देय'; + @override + String get filterDrawerPriority => 'प्राधान्य'; + @override + String get filterDrawerProject => 'प्रकल्प'; + @override + String get filterDrawerTags => 'टॅग्ज'; + @override + String get filterDrawerUrgency => 'तातडी'; + @override + String get filterDrawerResetSort => 'क्रमवारी रीसेट करा'; + @override + String get filterDrawerStatus => 'स्थिति'; + + @override + String get reportsPageTitle => 'अहवाल'; + @override + String get reportsPageCompleted => 'पूर्ण'; + @override + String get reportsPagePending => 'प्रलंबित'; + @override + String get reportsPageTasks => 'काम'; + + @override + String get reportsPageDaily => 'दैनिक'; + @override + String get reportsPageDailyBurnDownChart => 'दैनिक बर्न डाउन चार्ट'; + @override + String get reportsPageDailyDayMonth => 'दिवस - महिना'; + + @override + String get reportsPageWeekly => 'साप्ताहिक'; + @override + String get reportsPageWeeklyBurnDownChart => 'साप्ताहिक बर्न डाउन चार्ट'; + @override + String get reportsPageWeeklyWeeksYear => 'सप्ताह - वर्ष'; + + @override + String get reportsPageMonthly => 'मासिक'; + @override + String get reportsPageMonthlyBurnDownChart => 'मासिक बर्न डाउन चार्ट'; + @override + String get reportsPageMonthlyMonthYear => 'महिना - वर्ष'; + + @override + String get reportsPageNoTasksFound => 'कोणतेही काम सापडले नाहीत'; + @override + String get reportsPageAddTasksToSeeReports => 'अहवाल पाहण्यासाठी काम जोडा'; +} diff --git a/lib/app/utils/language/sentence_manager.dart b/lib/app/utils/language/sentence_manager.dart new file mode 100644 index 00000000..ce6ce1d0 --- /dev/null +++ b/lib/app/utils/language/sentence_manager.dart @@ -0,0 +1,24 @@ + +import 'package:taskwarrior/app/utils/language/english_sentences.dart'; +import 'package:taskwarrior/app/utils/language/hindi_sentences.dart'; +import 'package:taskwarrior/app/utils/language/marathi_sentences.dart'; +import 'package:taskwarrior/app/utils/language/sentences.dart'; +import 'package:taskwarrior/app/utils/language/supported_language.dart'; + +class SentenceManager { + final SupportedLanguage currentLanguage; + + SentenceManager({required this.currentLanguage}); + + Sentences get sentences { + switch (currentLanguage) { + case SupportedLanguage.hindi: + return HindiSentences(); + case SupportedLanguage.marathi: + return MarathiSentences(); + case SupportedLanguage.english: + default: + return EnglishSentences(); + } + } +} diff --git a/lib/app/utils/language/sentences.dart b/lib/app/utils/language/sentences.dart new file mode 100644 index 00000000..ac050117 --- /dev/null +++ b/lib/app/utils/language/sentences.dart @@ -0,0 +1,102 @@ +abstract class Sentences { + String get helloWorld; + + String get homePageTitle; + String get homePageLastModified; + String get homePageDue; + String get homePageTaskWarriorNotConfigured; + String get homePageSetup; + String get homePageFilter; + String get homePageMenu; + String get homePageExitApp; + String get homePageAreYouSureYouWantToExit; + String get homePageExit; + String get homePageCancel; + String get homePageClickOnTheBottomRightButtonToStartAddingTasks; + String get homePageSearchNotFound; + + String get settingsPageTitle; + String get settingsPageSubtitle; + String get settingsPageMovingDataToNewDirectory; + String get settingsPageChangeDirectory; + String get settingsPageSetToDefault; + + String get settingsPageSyncOnStartTitle; + String get settingsPageSyncOnStartDescription; + + String get settingsPageEnableSyncOnTaskCreateTitle; + String get settingsPageEnableSyncOnTaskCreateDescription; + + String get settingsPageHighlightTaskTitle; + String get settingsPageHighlightTaskDescription; + + String get settingsPageEnable24hrFormatTitle; + String get settingsPageEnable24hrFormatDescription; + + String get settingsPageSelectLanguage; + String get settingsPageToggleNativeLanguage; + + String get settingsPageSelectDirectoryTitle; + String get settingsPageSelectDirectoryDescription; + + String get navDrawerProfile; + String get navDrawerReports; + String get navDrawerAbout; + String get navDrawerSettings; + String get navDrawerExit; + + String get detailPageDescription; + String get detailPageStatus; + String get detailPageEntry; + String get detailPageModified; + String get detailPageStart; + String get detailPageEnd; + String get detailPageDue; + String get detailPageWait; + String get detailPageUntil; + String get detailPagePriority; + String get detailPageProject; + String get detailPageTags; + String get detailPageUrgency; + String get detailPageID; + + String get filterDrawerApplyFilters; + String get filterDrawerHideWaiting; + String get filterDrawerShowWaiting; + String get filterDrawerPending; + String get filterDrawerCompleted; + String get filterDrawerFilterTagBy; + String get filterDrawerAND; + String get filterDrawerOR; + String get filterDrawerSortBy; + String get filterDrawerCreated; + String get filterDrawerModified; + String get filterDrawerStartTime; + String get filterDrawerDueTill; + String get filterDrawerPriority; + String get filterDrawerProject; + String get filterDrawerTags; + String get filterDrawerUrgency; + String get filterDrawerResetSort; + String get filterDrawerStatus; + + String get reportsPageTitle; + String get reportsPageCompleted; + String get reportsPagePending; + String get reportsPageTasks; + + String get reportsPageDaily; + String get reportsPageDailyBurnDownChart; + String get reportsPageDailyDayMonth; + + String get reportsPageWeekly; + String get reportsPageWeeklyBurnDownChart; + String get reportsPageWeeklyWeeksYear; + + String get reportsPageMonthly; + String get reportsPageMonthlyBurnDownChart; + String get reportsPageMonthlyMonthYear; + + String get reportsPageNoTasksFound; + String get reportsPageAddTasksToSeeReports; +} diff --git a/lib/app/utils/language/supported_language.dart b/lib/app/utils/language/supported_language.dart new file mode 100644 index 00000000..8ad0a877 --- /dev/null +++ b/lib/app/utils/language/supported_language.dart @@ -0,0 +1,33 @@ +enum SupportedLanguage { + english, + hindi, + marathi, +} + +extension SupportedLanguageExtension on SupportedLanguage { + String get languageCode { + switch (this) { + case SupportedLanguage.english: + return 'en'; + case SupportedLanguage.hindi: + return 'hi'; + case SupportedLanguage.marathi: + return 'mr'; + default: + return ''; + } + } + + static SupportedLanguage? fromCode(String? code) { + switch (code) { + case 'en': + return SupportedLanguage.english; + case 'hi': + return SupportedLanguage.hindi; + case 'mr': + return SupportedLanguage.marathi; + default: + return null; + } + } +} diff --git a/lib/widgets/taskc/impl/codec.dart b/lib/app/utils/taskc/impl/codec.dart similarity index 100% rename from lib/widgets/taskc/impl/codec.dart rename to lib/app/utils/taskc/impl/codec.dart diff --git a/lib/widgets/taskc/impl/message.dart b/lib/app/utils/taskc/impl/message.dart similarity index 100% rename from lib/widgets/taskc/impl/message.dart rename to lib/app/utils/taskc/impl/message.dart diff --git a/lib/widgets/taskc/message.dart b/lib/app/utils/taskc/message.dart similarity index 84% rename from lib/widgets/taskc/message.dart rename to lib/app/utils/taskc/message.dart index 5b015df2..d492e485 100644 --- a/lib/widgets/taskc/message.dart +++ b/lib/app/utils/taskc/message.dart @@ -1,4 +1,4 @@ -import 'package:taskwarrior/widgets/taskserver.dart'; +import 'package:taskwarrior/app/utils/taskserver/credentials.dart'; String message({ String? client, diff --git a/lib/widgets/taskc/payload.dart b/lib/app/utils/taskc/payload.dart similarity index 100% rename from lib/widgets/taskc/payload.dart rename to lib/app/utils/taskc/payload.dart diff --git a/lib/widgets/taskc/response.dart b/lib/app/utils/taskc/response.dart similarity index 90% rename from lib/widgets/taskc/response.dart rename to lib/app/utils/taskc/response.dart index 9f0dc64f..5e5edf7e 100644 --- a/lib/widgets/taskc/response.dart +++ b/lib/app/utils/taskc/response.dart @@ -1,4 +1,4 @@ -import 'package:taskwarrior/widgets/taskc/payload.dart'; +import 'package:taskwarrior/app/utils/taskc/payload.dart'; class Response { Response({required this.header, required this.payload}); diff --git a/lib/widgets/taskfunctions/comparator.dart b/lib/app/utils/taskfunctions/comparator.dart similarity index 93% rename from lib/widgets/taskfunctions/comparator.dart rename to lib/app/utils/taskfunctions/comparator.dart index fca9c564..a20300e2 100644 --- a/lib/widgets/taskfunctions/comparator.dart +++ b/lib/app/utils/taskfunctions/comparator.dart @@ -1,9 +1,9 @@ import 'dart:math'; -import 'package:taskwarrior/model/json.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; +import 'package:taskwarrior/app/models/json/task.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/urgency.dart'; + -// ignore: prefer_expression_function_bodies int Function(Task, Task) compareTasks(String column) { return (a, b) { int? result; diff --git a/lib/widgets/taskfunctions/datetime_differences.dart b/lib/app/utils/taskfunctions/datetime_differences.dart similarity index 100% rename from lib/widgets/taskfunctions/datetime_differences.dart rename to lib/app/utils/taskfunctions/datetime_differences.dart diff --git a/lib/widgets/taskfunctions/draft.dart b/lib/app/utils/taskfunctions/draft.dart similarity index 82% rename from lib/widgets/taskfunctions/draft.dart rename to lib/app/utils/taskfunctions/draft.dart index ce5545fc..ce6db637 100644 --- a/lib/widgets/taskfunctions/draft.dart +++ b/lib/app/utils/taskfunctions/draft.dart @@ -1,5 +1,6 @@ -import 'package:taskwarrior/model/json.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; + +import 'package:taskwarrior/app/models/models.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/patch.dart'; class Draft { Draft( diff --git a/lib/widgets/taskfunctions/modify.dart b/lib/app/utils/taskfunctions/modify.dart similarity index 94% rename from lib/widgets/taskfunctions/modify.dart rename to lib/app/utils/taskfunctions/modify.dart index 94751bf3..8ff98855 100644 --- a/lib/widgets/taskfunctions/modify.dart +++ b/lib/app/utils/taskfunctions/modify.dart @@ -1,9 +1,8 @@ // ignore_for_file: depend_on_referenced_packages import 'package:collection/collection.dart'; - -import 'package:taskwarrior/model/json.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; +import 'package:taskwarrior/app/models/models.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/draft.dart'; class Modify { Modify({ diff --git a/lib/widgets/taskfunctions/patch.dart b/lib/app/utils/taskfunctions/patch.dart similarity index 96% rename from lib/widgets/taskfunctions/patch.dart rename to lib/app/utils/taskfunctions/patch.dart index f398a239..7f7e3aba 100644 --- a/lib/widgets/taskfunctions/patch.dart +++ b/lib/app/utils/taskfunctions/patch.dart @@ -1,7 +1,7 @@ // ignore_for_file: depend_on_referenced_packages import 'package:built_collection/built_collection.dart'; -import 'package:taskwarrior/model/json.dart'; +import 'package:taskwarrior/app/models/models.dart'; Task patch(Task task, Map updates) { return updates.entries.fold( diff --git a/lib/widgets/taskfunctions/profiles.dart b/lib/app/utils/taskfunctions/profiles.dart similarity index 98% rename from lib/widgets/taskfunctions/profiles.dart rename to lib/app/utils/taskfunctions/profiles.dart index ff592d07..c8d85ff7 100644 --- a/lib/widgets/taskfunctions/profiles.dart +++ b/lib/app/utils/taskfunctions/profiles.dart @@ -3,10 +3,9 @@ import 'dart:collection'; import 'dart:io'; +import 'package:taskwarrior/app/models/storage.dart'; import 'package:uuid/uuid.dart'; -import 'package:taskwarrior/model/storage.dart'; - class Profiles { Profiles(this.base); diff --git a/lib/widgets/taskfunctions/projects.dart b/lib/app/utils/taskfunctions/projects.dart similarity index 100% rename from lib/widgets/taskfunctions/projects.dart rename to lib/app/utils/taskfunctions/projects.dart diff --git a/lib/widgets/taskfunctions/query.dart b/lib/app/utils/taskfunctions/query.dart similarity index 100% rename from lib/widgets/taskfunctions/query.dart rename to lib/app/utils/taskfunctions/query.dart diff --git a/lib/widgets/taskfunctions/tags.dart b/lib/app/utils/taskfunctions/tags.dart similarity index 95% rename from lib/widgets/taskfunctions/tags.dart rename to lib/app/utils/taskfunctions/tags.dart index 490b07f6..338255e9 100644 --- a/lib/widgets/taskfunctions/tags.dart +++ b/lib/app/utils/taskfunctions/tags.dart @@ -1,6 +1,8 @@ import 'dart:math'; -import 'package:taskwarrior/model/json.dart'; +import 'package:taskwarrior/app/models/models.dart'; + + Set tagSet(Iterable tasks) { return tasks.where((task) => task.tags != null).fold( diff --git a/lib/widgets/taskfunctions/taskparser.dart b/lib/app/utils/taskfunctions/taskparser.dart similarity index 98% rename from lib/widgets/taskfunctions/taskparser.dart rename to lib/app/utils/taskfunctions/taskparser.dart index 16d0947a..b83fea81 100644 --- a/lib/widgets/taskfunctions/taskparser.dart +++ b/lib/app/utils/taskfunctions/taskparser.dart @@ -1,9 +1,11 @@ // ignore_for_file: depend_on_referenced_packages, deprecated_member_use import 'package:petitparser/petitparser.dart'; +import 'package:taskwarrior/app/models/models.dart'; + import 'package:uuid/uuid.dart'; -import 'package:taskwarrior/model/json.dart'; + class Tag { const Tag(this.tag); diff --git a/lib/widgets/taskfunctions/urgency.dart b/lib/app/utils/taskfunctions/urgency.dart similarity index 98% rename from lib/widgets/taskfunctions/urgency.dart rename to lib/app/utils/taskfunctions/urgency.dart index d9ef9a12..4a03ae3d 100644 --- a/lib/widgets/taskfunctions/urgency.dart +++ b/lib/app/utils/taskfunctions/urgency.dart @@ -1,4 +1,6 @@ -import 'package:taskwarrior/model/json.dart'; + + +import 'package:taskwarrior/app/models/models.dart'; String formatUrgency(double urgency) { var result = urgency.toStringAsFixed(2); diff --git a/lib/widgets/taskfunctions/validate.dart b/lib/app/utils/taskfunctions/validate.dart similarity index 100% rename from lib/widgets/taskfunctions/validate.dart rename to lib/app/utils/taskfunctions/validate.dart diff --git a/lib/widgets/taskserver/credentials.dart b/lib/app/utils/taskserver/credentials.dart similarity index 100% rename from lib/widgets/taskserver/credentials.dart rename to lib/app/utils/taskserver/credentials.dart diff --git a/lib/widgets/taskserver/parse_taskrc.dart b/lib/app/utils/taskserver/parse_taskrc.dart similarity index 100% rename from lib/widgets/taskserver/parse_taskrc.dart rename to lib/app/utils/taskserver/parse_taskrc.dart diff --git a/lib/widgets/taskserver/pem_file_paths.dart b/lib/app/utils/taskserver/pem_file_paths.dart similarity index 100% rename from lib/widgets/taskserver/pem_file_paths.dart rename to lib/app/utils/taskserver/pem_file_paths.dart diff --git a/lib/widgets/taskserver/server.dart b/lib/app/utils/taskserver/server.dart similarity index 91% rename from lib/widgets/taskserver/server.dart rename to lib/app/utils/taskserver/server.dart index bb4ce2c8..fec6752b 100644 --- a/lib/widgets/taskserver/server.dart +++ b/lib/app/utils/taskserver/server.dart @@ -1,4 +1,4 @@ -import 'package:taskwarrior/widgets/taskserver.dart'; +import 'package:taskwarrior/app/utils/taskserver/taskrc_exception.dart'; class Server { const Server({ diff --git a/lib/widgets/taskserver/taskrc.dart b/lib/app/utils/taskserver/taskrc.dart similarity index 73% rename from lib/widgets/taskserver/taskrc.dart rename to lib/app/utils/taskserver/taskrc.dart index 76038410..36348b9a 100644 --- a/lib/widgets/taskserver/taskrc.dart +++ b/lib/app/utils/taskserver/taskrc.dart @@ -1,4 +1,7 @@ -import 'package:taskwarrior/widgets/taskserver.dart'; +import 'package:taskwarrior/app/utils/taskserver/credentials.dart'; +import 'package:taskwarrior/app/utils/taskserver/parse_taskrc.dart'; +import 'package:taskwarrior/app/utils/taskserver/pem_file_paths.dart'; +import 'package:taskwarrior/app/utils/taskserver/server.dart'; class Taskrc { Taskrc({ diff --git a/lib/widgets/taskserver/taskrc_exception.dart b/lib/app/utils/taskserver/taskrc_exception.dart similarity index 100% rename from lib/widgets/taskserver/taskrc_exception.dart rename to lib/app/utils/taskserver/taskrc_exception.dart diff --git a/lib/app/utils/taskserver/taskserver.dart b/lib/app/utils/taskserver/taskserver.dart new file mode 100644 index 00000000..db5cfa49 --- /dev/null +++ b/lib/app/utils/taskserver/taskserver.dart @@ -0,0 +1,6 @@ +export 'credentials.dart'; +export 'parse_taskrc.dart'; +export 'pem_file_paths.dart'; +export 'server.dart'; +export 'taskrc.dart'; +export 'taskrc_exception.dart'; diff --git a/lib/app/utils/theme/app_settings.dart b/lib/app/utils/theme/app_settings.dart new file mode 100644 index 00000000..d4f6e1d2 --- /dev/null +++ b/lib/app/utils/theme/app_settings.dart @@ -0,0 +1,96 @@ +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:taskwarrior/app/utils/language/supported_language.dart'; + +class SelectedTheme { + static SharedPreferences? _preferences; + + static Future init() async { + _preferences = await SharedPreferences.getInstance(); + } + + static Future saveMode(bool mode) async { + await _preferences?.setBool('_isDark', mode); + } + + static bool? getMode() { + return _preferences?.getBool('_isDark'); + } +} + +class SelectedLanguage { + static SharedPreferences? _preferences; + + static Future init() async { + _preferences = await SharedPreferences.getInstance(); + } + + static Future saveSelectedLanguage(SupportedLanguage language) async { + await _preferences?.setString('_selectedLanguage', language.languageCode); + } + + static SupportedLanguage? getSelectedLanguage() { + String? languageCode = _preferences?.getString('_selectedLanguage'); + return SupportedLanguageExtension.fromCode(languageCode); + } +} + +class SaveTourStatus { + static SharedPreferences? _preferences; + + static Future init() async { + _preferences = await SharedPreferences.getInstance(); + } + + static Future saveReportsTourStatus(bool status) async { + await _preferences?.setBool('reports_tour', status); + } + + static Future getReportsTourStatus() async { + return _preferences?.getBool('reports_tour') ?? false; + } + + static Future saveInAppTourStatus(bool status) async { + await _preferences?.setBool('tour', status); + } + + static Future getInAppTourStatus() async { + return _preferences?.getBool('tour') ?? false; + } + + static Future saveFilterTourStatus(bool status) async { + await _preferences?.setBool('filter_tour', status); + } + + static Future getFilterTourStatus() async { + return _preferences?.getBool('filter_tour') ?? false; + } + + static Future saveProfileTourStatus(bool status) async { + await _preferences?.setBool('profile_tour', status); + } + + static Future getProfileTourStatus() async { + return _preferences?.getBool('profile_tour') ?? false; + } +} + +class AppSettings { + static bool isDarkMode = true; + static SupportedLanguage selectedLanguage = SupportedLanguage.english; + + static Future init() async { + await SelectedTheme.init(); + await SelectedLanguage.init(); + await SaveTourStatus.init(); + + isDarkMode = SelectedTheme.getMode() ?? true; + selectedLanguage = + SelectedLanguage.getSelectedLanguage() ?? SupportedLanguage.english; + } + + static Future saveSettings( + bool isDarkMode, SupportedLanguage language) async { + await SelectedTheme.saveMode(isDarkMode); + await SelectedLanguage.saveSelectedLanguage(language); + } +} diff --git a/lib/config/app_settings.dart b/lib/config/app_settings.dart deleted file mode 100644 index b513d003..00000000 --- a/lib/config/app_settings.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:shared_preferences/shared_preferences.dart'; - -class SelectedTheme { - static SharedPreferences? _preferences; - - static Future init() async => - _preferences = await SharedPreferences.getInstance(); - - static Future saveMode(bool mode) async => - await _preferences?.setBool('_isDark', mode); - - static bool? getMode() => _preferences?.getBool('_isDark'); -} - -class AppSettings { - static bool isDarkMode = SelectedTheme.getMode() ?? true; -} diff --git a/lib/controller/filter_drawer_tour_controller.dart b/lib/controller/filter_drawer_tour_controller.dart deleted file mode 100644 index 3cfcec57..00000000 --- a/lib/controller/filter_drawer_tour_controller.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:shared_preferences/shared_preferences.dart'; - -class SaveFilterTour { - Future data = SharedPreferences.getInstance(); - - void saveFilterTourStatus() async { - final value = await data; - value.setBool('filter_tour', true); - } - - Future getFilterTourStatus() async { - final value = await data; - if (value.containsKey('filter_tour')) { - bool? getData = value.getBool('filter_tour'); - return getData!; - } else { - return false; - } - } -} diff --git a/lib/controller/home_tour_controller.dart b/lib/controller/home_tour_controller.dart deleted file mode 100644 index 6d4b2f50..00000000 --- a/lib/controller/home_tour_controller.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:shared_preferences/shared_preferences.dart'; - -class SaveInAppTour { - Future data = SharedPreferences.getInstance(); - - void saveTourStatus() async { - final value = await data; - value.setBool('tour', true); - } - - Future getTourStatus() async { - final value = await data; - if (value.containsKey('tour')) { - bool? getData = value.getBool('tour'); - return getData!; - } else { - return false; - } - } -} diff --git a/lib/controller/onboarding_controller.dart b/lib/controller/onboarding_controller.dart deleted file mode 100644 index f6aa269f..00000000 --- a/lib/controller/onboarding_controller.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:get/get.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -class OnboardingController extends GetxController { - RxBool hasCompletedOnboarding = false.obs; - - @override - void onInit() { - super.onInit(); - // Check onboarding status when the controller is initialized - checkOnboardingStatus(); - } - - Future checkOnboardingStatus() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - // Retrieve the completion status (default to false if not found) - hasCompletedOnboarding.value = - prefs.getBool('onboarding_completed') ?? false; - } - - Future markOnboardingAsCompleted() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - await prefs.setBool('onboarding_completed', true); - // Update the completion status - hasCompletedOnboarding.value = true; - } -} diff --git a/lib/controller/reports_tour_controller.dart b/lib/controller/reports_tour_controller.dart deleted file mode 100644 index 35e3162b..00000000 --- a/lib/controller/reports_tour_controller.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:shared_preferences/shared_preferences.dart'; - -class SaveReportsTour { - Future data = SharedPreferences.getInstance(); - - void saveReportsTourStatus() async { - final value = await data; - value.setBool('reports_tour', true); - } - - Future getReportsTourStatus() async { - final value = await data; - if (value.containsKey('reports_tour')) { - bool? getData = value.getBool('reports_tour'); - return getData!; - } else { - return false; - } - } -} diff --git a/lib/drawer/filter_drawer.dart b/lib/drawer/filter_drawer.dart deleted file mode 100644 index 45d21374..00000000 --- a/lib/drawer/filter_drawer.dart +++ /dev/null @@ -1,422 +0,0 @@ -// ignore_for_file: prefer_const_constructors - -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; - -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/controller/filter_drawer_tour_controller.dart'; -import 'package:taskwarrior/drawer/filter_drawer_tour.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/views/home/home.dart'; -import 'package:taskwarrior/widgets/project_filter.dart'; -import 'package:taskwarrior/widgets/tag_filter.dart'; -import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; - -class FilterDrawer extends StatefulWidget { - final Filters filters; - - const FilterDrawer(this.filters, {super.key}); - - @override - // ignore: library_private_types_in_public_api - _FilterDrawerState createState() => _FilterDrawerState(); -} - -class _FilterDrawerState extends State { - final GlobalKey statusKey = GlobalKey(); - final GlobalKey projectsKey = GlobalKey(); - final GlobalKey filterTagKey = GlobalKey(); - final GlobalKey sortByKey = GlobalKey(); - - bool isSaved = false; - var tileColor = AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightPrimaryBackgroundColor; - late TutorialCoachMark tutorialCoachMark; - - void _initFilterDrawerTour() { - tutorialCoachMark = TutorialCoachMark( - targets: filterDrawer( - statusKey: statusKey, - projectsKey: projectsKey, - filterTagKey: filterTagKey, - sortByKey: sortByKey, - ), - colorShadow: TaskWarriorColors.black, - paddingFocus: 10, - opacityShadow: 1.00, - hideSkip: true, - onFinish: () { - SaveFilterTour().saveFilterTourStatus(); - }, - ); - } - - void _showFilterDrawerTour() { - Future.delayed( - const Duration(seconds: 2), - () { - SaveFilterTour().getFilterTourStatus().then((value) => { - if (value == false) - { - tutorialCoachMark.show(context: context), - } - else - { - // ignore: avoid_print - print('User has seen this page'), - } - }); - }, - ); - } - - @override - void initState() { - super.initState(); - _initFilterDrawerTour(); - _showFilterDrawerTour(); - } - - @override - Widget build(BuildContext context) { - var storageWidget = StorageWidget.of(context); - return Drawer( - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.kLightPrimaryBackgroundColor, - surfaceTintColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.kLightPrimaryBackgroundColor, - child: SafeArea( - child: Padding( - padding: const EdgeInsets.all(8), - child: ListView( - primary: false, - key: const PageStorageKey('tags-filter'), - children: [ - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - Container( - height: 45, - alignment: Alignment.center, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 0.0), - child: Text( - 'Apply Filters', - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - color: (AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightPrimaryTextColor), - fontSize: TaskWarriorFonts.fontSizeExtraLarge), - ), - ), - ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - Container( - // width: MediaQuery.of(context).size.width * 1, - // padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: tileColor, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: TaskWarriorColors.borderColor), - ), - child: ListTile( - contentPadding: EdgeInsets.only( - left: 8, - ), - title: RichText( - key: statusKey, - maxLines: 2, - text: TextSpan( - children: [ - TextSpan( - text: 'Status : ', - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode ? TaskWarriorColors.white : TaskWarriorColors.black, - ), - ), - TextSpan( - text: widget.filters.pendingFilter ? 'pending' : 'completed', - style: GoogleFonts.poppins( - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode ? TaskWarriorColors.white : TaskWarriorColors.black, - ), - ), - ], - ), - ), - onTap: widget.filters.togglePendingFilter, - textColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightSecondaryTextColor, - ), - ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - Container( - decoration: BoxDecoration( - color: tileColor, - borderRadius: BorderRadius.circular(2), - border: Border.all(color: TaskWarriorColors.borderColor), - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: GestureDetector( - onTap: - widget.filters.toggleWaitingFilter, - child: Text( - widget.filters.waitingFilter ? 'Show waiting': - 'Hide waiting', - style: GoogleFonts.poppins( - color: (AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightSecondaryTextColor), - // - fontSize: TaskWarriorFonts.fontSizeMedium,), - ), - ), - ), - ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - Container( - key: projectsKey, - width: MediaQuery.of(context).size.width * 1, - // padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: tileColor, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: TaskWarriorColors.borderColor), - ), - child: ProjectsColumn( - widget.filters.projects, - widget.filters.projectFilter, - widget.filters.toggleProjectFilter, - ), - ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - Container( - key: filterTagKey, - width: MediaQuery.of(context).size.width * 1, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: tileColor, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: TaskWarriorColors.borderColor), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 0.0), - child: Text( - 'Filter Tag By:', - style: GoogleFonts.poppins( - color: (AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightSecondaryTextColor), - // - fontSize: TaskWarriorFonts.fontSizeLarge), - //textAlign: TextAlign.right, - ), - ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: TagFiltersWrap(widget.filters.tagFilters), - ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - ], - ), - ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - Container( - key: sortByKey, - width: MediaQuery.of(context).size.width * 1, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: tileColor, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: TaskWarriorColors.borderColor), - ), - //height: 30, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 0.0), - child: Text( - 'Sort By', - style: GoogleFonts.poppins( - color: (AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightPrimaryTextColor), - fontSize: TaskWarriorFonts.fontSizeLarge), - // textAlign: TextAlign.right, - ), - ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Wrap( - spacing: 8, - runSpacing: 4, - children: [ - for (var sort in [ - 'Created', - 'Modified', - 'Start Time', - 'Due till', - 'Priority', - 'Project', - 'Tags', - 'Urgency', - ]) - ChoiceChip( - label: (storageWidget.selectedSort.startsWith(sort)) - ? Text( - storageWidget.selectedSort, - ) - : Text(sort), - selected: false, - onSelected: (_) { - if (storageWidget.selectedSort == '$sort+') { - storageWidget.selectSort('$sort-'); - } else if (storageWidget.selectedSort == '$sort-') { - storageWidget.selectSort(sort); - } else { - storageWidget.selectSort('$sort+'); - } - }, - labelStyle: GoogleFonts.poppins( - color: AppSettings.isDarkMode ? TaskWarriorColors.black : TaskWarriorColors.white), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kLightSecondaryBackgroundColor - : TaskWarriorColors.ksecondaryBackgroundColor, - ), - ], - ), - ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - Container( - width: 200, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: AppSettings.isDarkMode - ? TaskWarriorColors.kLightSecondaryBackgroundColor - : TaskWarriorColors.ksecondaryBackgroundColor), - child: TextButton( - onPressed: () { - if (storageWidget.selectedSort.endsWith('+') || storageWidget.selectedSort.endsWith('-')) { - storageWidget.selectSort( - storageWidget.selectedSort.substring(0, storageWidget.selectedSort.length - 1)); - } - }, - child: Text( - 'Reset Sort', - style: GoogleFonts.poppins( - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.kLightSecondaryTextColor - : TaskWarriorColors.ksecondaryTextColor), - )), - ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - ], - ), - ) - ], - ), - ), - ), - ); - } -} - -// class TagFilterMetadata { -// const TagFilterMetadata({ -// required this.display, -// required this.selected, -// }); - -// final String display; -// final bool selected; -// } - -// class TagFilters { -// const TagFilters({ -// required this.tagUnion, -// required this.toggleTagUnion, -// required this.tags, -// required this.toggleTagFilter, -// }); - -// final bool tagUnion; -// final void Function() toggleTagUnion; -// final Map tags; -// final void Function(String) toggleTagFilter; -// } - -// class TagFiltersWrap extends StatelessWidget { -// const TagFiltersWrap(this.filters, {super.key}); - -// final TagFilters filters; - -// @override -// Widget build(BuildContext context) { -// return Wrap( -// spacing: 4, -// children: [ -// FilterChip( -// onSelected: (_) => filters.toggleTagUnion(), -// label: Text(filters.tagUnion ? 'OR' : 'AND', -// style: GoogleFonts.poppins())), -// for (var entry in filters.tags.entries) -// FilterChip( -// onSelected: (_) => filters.toggleTagFilter(entry.key), -// label: Text( -// entry.value.display, -// style: GoogleFonts.poppins( -// fontWeight: entry.value.selected ? FontWeight.w700 : null, -// ), -// ), -// ), -// ], -// ); -// } -// } diff --git a/lib/drawer/nav_drawer.dart b/lib/drawer/nav_drawer.dart deleted file mode 100644 index c05a0fa6..00000000 --- a/lib/drawer/nav_drawer.dart +++ /dev/null @@ -1,267 +0,0 @@ -// ignore_for_file: library_private_types_in_public_api, use_build_context_synchronously - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:sizer/sizer.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/utility/utilities.dart'; -import 'package:taskwarrior/views/about/about.dart'; -import 'package:taskwarrior/views/profile/profile.dart'; -import 'package:taskwarrior/views/reports/reports_home.dart'; -import 'package:taskwarrior/config/theme_switcher_clipper.dart'; -import 'package:taskwarrior/views/settings/settings.dart'; - -class NavDrawer extends StatefulWidget { - final InheritedStorage storageWidget; - final Function() notifyParent; - - const NavDrawer({ - super.key, - required this.storageWidget, - required this.notifyParent, - }); - - @override - _NavDrawerState createState() => _NavDrawerState(); -} - -class _NavDrawerState extends State { - @override - Widget build(BuildContext context) { - return Drawer( - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.kLightPrimaryBackgroundColor, - surfaceTintColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.kLightPrimaryBackgroundColor, - child: Container( - color: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.kLightPrimaryBackgroundColor, - child: ListView( - padding: EdgeInsets.zero, - children: [ - Container( - color: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.kLightPrimaryBackgroundColor, - padding: const EdgeInsets.only(top: 50, left: 15, right: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Menu', - style: TextStyle( - fontSize: TaskWarriorFonts.fontSizeExtraLarge, - fontWeight: TaskWarriorFonts.bold, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - Padding( - padding: const EdgeInsets.only(right: 10), - child: ThemeSwitcherClipper( - isDarkMode: AppSettings.isDarkMode, - onTap: (bool newMode) async { - AppSettings.isDarkMode = newMode; - setState(() {}); - await SelectedTheme.saveMode(AppSettings.isDarkMode); - widget.notifyParent(); - }, - child: Icon( - AppSettings.isDarkMode - ? Icons.dark_mode - : Icons.light_mode, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - size: 15, - ), - ), - ), - ], - ), - ), - Container( - color: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.kLightPrimaryBackgroundColor, - height: 3.h, - ), - buildMenuItem( - icon: Icons.person_rounded, - text: 'Profile', - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const ProfilePage(), - ), - ); - // Navigator.pushNamed(context, PageRoutes.profile); - }, - ), - buildMenuItem( - icon: Icons.summarize, - text: 'Reports', - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const ReportsHome(), - ), - ); - }, - ), - buildMenuItem( - icon: Icons.info, - text: 'About', - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const AboutPage(), - ), - ); - }, - ), - buildMenuItem( - icon: Icons.settings, - text: 'Settings', - onTap: () async { - bool syncOnStart = false; - bool syncOnTaskCreate = false; - bool delaytask = false; - bool change24hr = false; - - ///check if auto sync is on or off - final SharedPreferences prefs = - await SharedPreferences.getInstance(); - setState(() { - syncOnStart = prefs.getBool('sync-onStart') ?? false; - syncOnTaskCreate = - prefs.getBool('sync-OnTaskCreate') ?? false; - delaytask = prefs.getBool('delaytask') ?? false; - change24hr = prefs.getBool('24hourformate') ?? false; - }); - // print(syncOnStart); - // print(syncOnTaskCreate); - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => SettingsPage( - isSyncOnStartActivel: syncOnStart, - isSyncOnTaskCreateActivel: syncOnTaskCreate, - delaytask: delaytask, - change24hr: change24hr, - ), - ), - ); - }, - ), - buildMenuItem( - icon: Icons.exit_to_app, - text: 'Exit', - onTap: () { - _showExitConfirmationDialog(context); - }, - ), - ], - ), - ), - ); - } - - Widget buildMenuItem( - {required IconData icon, - required String text, - required VoidCallback onTap}) { - return InkWell( - onTap: onTap, - child: Container( - color: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.kLightPrimaryBackgroundColor, - padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 10), - child: Row( - children: [ - Icon( - icon, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - const SizedBox(width: 10), - Text( - text, - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - fontSize: TaskWarriorFonts.fontSizeMedium, - ), - ), - ], - ), - ), - ); - } -} - -Future _showExitConfirmationDialog(BuildContext context) async { - return showDialog( - context: context, - barrierDismissible: false, // Prevents closing the dialog by tapping outside - builder: (BuildContext context) { - return Utils.showAlertDialog( - title: Text( - 'Exit App', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - content: Text( - 'Are you sure you want to exit the app?', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - actions: [ - TextButton( - child: Text( - 'Cancel', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - onPressed: () { - Navigator.of(context).pop(); // Close the dialog - }, - ), - TextButton( - child: Text( - 'Exit', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - onPressed: () { - Navigator.of(context).pop(); // Close the dialog - SystemNavigator.pop(); // Exit the app - }, - ), - ], - ); - }, - ); -} diff --git a/lib/main.dart b/lib/main.dart index f6c806a5..eff687ba 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,225 +1,24 @@ -// ignore_for_file: depend_on_referenced_packages - -import 'dart:io'; - import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:get/get.dart'; -import 'package:home_widget/home_widget.dart'; -import 'package:in_app_update/in_app_update.dart'; -import 'package:loggy/loggy.dart'; -import 'package:path_provider/path_provider.dart'; +import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:sizer/sizer.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/controller/WidgetController.dart'; -import 'package:taskwarrior/controller/onboarding_controller.dart'; -import 'package:taskwarrior/routes/pageroute.dart'; -import 'package:taskwarrior/services/task_details.dart'; -import 'package:taskwarrior/views/Onboarding/onboarding_screen.dart'; -import 'package:taskwarrior/views/profile/profile.dart'; -import 'package:taskwarrior/widgets/app_placeholder.dart'; -import 'package:uuid/uuid.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; -import 'package:taskwarrior/services/notification_services.dart'; -import 'package:taskwarrior/views/home/home.dart'; -import 'package:taskwarrior/widgets/pallete.dart'; -import 'package:taskwarrior/widgets/taskdetails/profiles_widget.dart'; +import 'app/routes/app_pages.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; - -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:taskwarrior/model/json/task.dart'; -import 'package:taskwarrior/model/storage.dart'; - -Future main([List args = const []]) async { +void main() async { WidgetsFlutterBinding.ensureInitialized(); + await AppSettings.init(); await Permission.notification.isDenied.then((value) { if (value) { Permission.notification.request(); } }); - - Directory? testingDirectory; - if (args.contains('flutter_driver_test')) { - testingDirectory = Directory( - '${Directory.systemTemp.path}/flutter_driver_test/${const Uuid().v1()}', - )..createSync(recursive: true); - stdout.writeln(testingDirectory); - Directory( - '${testingDirectory.path}/profiles/acae0462-6a34-11e4-8001-002590720087', - ).createSync(recursive: true); - } - SystemChrome.setPreferredOrientations( - [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]) - .then((value) => runApp(FutureBuilder>( - future: getDirectories(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return ProfilesWidget( - defaultDirectory: snapshot.data![0], - baseDirectory: testingDirectory ?? snapshot.data![1], - child: const MyApp(), - ); - } else { - return const AppSetupPlaceholder(); - } - }))); -} - -Future> getDirectories() async { - Directory defaultDirectory = await getApplicationDocumentsDirectory(); - SharedPreferences prefs = await SharedPreferences.getInstance(); - String? directory = prefs.getString('baseDirectory'); - Directory baseDirectory = - (directory != null) ? Directory(directory) : defaultDirectory; - return [defaultDirectory, baseDirectory]; -} - -Future init() async { - Loggy.initLoggy(logPrinter: const PrettyPrinter()); -} - -class MyApp extends StatefulWidget { - const MyApp({super.key}); - @override - // ignore: library_private_types_in_public_api - _MyAppState createState() => _MyAppState(); -} - -// ignore: use_key_in_widget_constructors -class _MyAppState extends State { - NotificationService notificationService = NotificationService(); - late InheritedStorage storageWidget; - - late Storage storage; - late final Filters filters; - List taskData = []; - List dailyBurnDown = []; - Directory? baseDirectory; - List allData = []; - bool stopTraver = false; - - bool isHomeWidgetTaskTapped = false; - late String uuid; - @override - void initState() { - super.initState(); - - ///sort the data by daily burn down - - notificationService.initiliazeNotification(); - helperFunction(); - } - - Future checkForUpdate() async { - // print('checking for Update'); - InAppUpdate.checkForUpdate().then((info) { - setState(() { - if (info.updateAvailability == UpdateAvailability.updateAvailable) { - // print('update available'); - update(); - } - }); - }).catchError((e) { - // print(e.toString()); - }); - } - - void update() async { - // print('Updating'); - await InAppUpdate.startFlexibleUpdate(); - InAppUpdate.completeFlexibleUpdate().then((_) {}).catchError((e) { - // print(e.toString()); - }); - } - - void helperFunction() async { - Uri? myUri = await HomeWidget.initiallyLaunchedFromHomeWidget(); - if (myUri != null) { - if (myUri.host == "cardclicked") { - if (myUri.queryParameters["uuid"] != null) { - uuid = myUri.queryParameters["uuid"] as String; - setState(() { - isHomeWidgetTaskTapped = true; - }); - // print('is tapped is $isHomeWidgetTaskTapped'); - } - // debugPrint('uuid is $uuid'); - } - } - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - } - - @override - Widget build(BuildContext context) { - WidgetController widgetController = Get.put(WidgetController(context)); - widgetController.fetchAllData(); - - return Sizer(builder: ((context, orientation, deviceType) { - return GetMaterialApp( - builder: (context, child) { - return Builder( - builder: (BuildContext context) { - return child!; - }, - ); - }, - debugShowCheckedModeBanner: false, - title: 'Taskwarrior', - theme: ThemeData( - useMaterial3: true, - primarySwatch: Palette.kToDark, - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - // initialRoute: PageRoutes.home, - routes: { - PageRoutes.home: (context) => const HomePage(), - PageRoutes.profile: (context) => const ProfilePage(), - }, - - home: isHomeWidgetTaskTapped == false - ? CheckOnboardingStatus() - : FutureBuilder( - future: Future.delayed(const Duration(seconds: 2)), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Scaffold( - backgroundColor: AppSettings.isDarkMode - ? Palette.kToDark.shade200 - : Colors.white, - body: const Center(child: CircularProgressIndicator())); - } - return DetailRoute(uuid); - }, - ), - ); - })); - } -} - -class CheckOnboardingStatus extends StatelessWidget { - final OnboardingController onboardingController = - Get.put(OnboardingController()); - - CheckOnboardingStatus({super.key}); - - @override - Widget build(BuildContext context) { - return Obx( - () { - if (onboardingController.hasCompletedOnboarding.value) { - return const HomePage(); - } else { - return const OnboardingScreen(); - } - }, - ); - } + runApp( + GetMaterialApp( + title: "Application", + initialRoute: AppPages.INITIAL, + getPages: AppPages.routes, + ), + ); } diff --git a/lib/model/json.dart b/lib/model/json.dart deleted file mode 100644 index 71e189aa..00000000 --- a/lib/model/json.dart +++ /dev/null @@ -1,9 +0,0 @@ -/// Custom JSON objects for various libraries of this package are organized -/// here. - -library json; - -export 'json/annotation.dart'; -export 'json/iso_8601_basic.dart'; -export 'json/serializers.dart'; -export 'json/task.dart'; diff --git a/lib/model/storage/storage_widget.dart b/lib/model/storage/storage_widget.dart deleted file mode 100644 index 4d900a61..00000000 --- a/lib/model/storage/storage_widget.dart +++ /dev/null @@ -1,555 +0,0 @@ -// ignore_for_file: use_build_context_synchronously - -import 'dart:collection'; -import 'dart:io'; - -import 'package:connectivity_plus/connectivity_plus.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:google_fonts/google_fonts.dart'; - -import 'package:loggy/loggy.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; - -import 'package:taskwarrior/model/json.dart'; -import 'package:taskwarrior/model/storage.dart'; -import 'package:taskwarrior/model/storage/client.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; - -class TagMetadata { - TagMetadata({ - required this.lastModified, - required this.frequency, - required this.selected, - }); - - final DateTime lastModified; - final int frequency; - final bool selected; - - Map toJson() => { - 'lastModified': lastModified, - 'frequency': frequency, - 'selected': selected, - }; - - @override - String toString() => toJson().toString(); -} - -class StorageWidget extends StatefulWidget { - const StorageWidget({super.key, required this.profile, required this.child}); - - final Directory profile; - final Widget child; - - @override - State createState() => _StorageWidgetState(); - - static InheritedStorage of(BuildContext context) { - return InheritedModel.inheritFrom(context)!; - } -} - -class _StorageWidgetState extends State { - late Storage storage; - late bool pendingFilter; - late bool waitingFilter; - late String projectFilter; - late bool tagUnion; - late String selectedSort; - late Set selectedTags; - late List queriedTasks; - late List searchedTasks; - late Map pendingTags; - late Map projects; - bool sortHeaderVisible = false; - bool searchVisible = false; - var searchController = TextEditingController(); - late bool serverCertExists; - - @override - void initState() { - super.initState(); - storage = Storage(widget.profile); - serverCertExists = storage.guiPemFiles.serverCertExists(); - _profileSet(); - } - - @override - void didUpdateWidget(oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.profile != oldWidget.profile) { - storage = Storage(widget.profile); - serverCertExists = storage.guiPemFiles.serverCertExists(); - _profileSet(); - } - } - - void _profileSet() { - pendingFilter = Query(storage.tabs.tab()).getPendingFilter(); - waitingFilter = Query(storage.tabs.tab()).getWaitingFilter(); - projectFilter = Query(storage.tabs.tab()).projectFilter(); - tagUnion = Query(storage.tabs.tab()).tagUnion(); - selectedSort = Query(storage.tabs.tab()).getSelectedSort(); - selectedTags = Query(storage.tabs.tab()).getSelectedTags(); - _refreshTasks(); - pendingTags = _pendingTags(); - projects = _projects(); - if (searchVisible) { - toggleSearch(); - } - } - - void _refreshTasks() { - if (pendingFilter) { - queriedTasks = storage.data - .pendingData() - .where((task) => task.status == 'pending') - .toList(); - } else { - queriedTasks = storage.data.completedData(); - } - - if (waitingFilter) { - var currentTime = DateTime.now(); - queriedTasks = queriedTasks - .where((task) => - task.wait == null || - task.wait!.isBefore(currentTime) || - task.wait!.isAtSameMomentAs(currentTime)) - .toList(); - } - - if (projectFilter.isNotEmpty) { - queriedTasks = queriedTasks.where((task) { - if (task.project == null) { - return false; - } else { - return task.project!.startsWith(projectFilter); - } - }).toList(); - } - - queriedTasks = queriedTasks.where((task) { - var tags = task.tags?.toSet() ?? {}; - if (tagUnion) { - if (selectedTags.isEmpty) { - return true; - } - return selectedTags.any((tag) => (tag.startsWith('+')) - ? tags.contains(tag.substring(1)) - : !tags.contains(tag.substring(1))); - } else { - return selectedTags.every((tag) => (tag.startsWith('+')) - ? tags.contains(tag.substring(1)) - : !tags.contains(tag.substring(1))); - } - }).toList(); - - var sortColumn = selectedSort.substring(0, selectedSort.length - 1); - var ascending = selectedSort.endsWith('+'); - queriedTasks.sort((a, b) { - int result; - if (sortColumn == 'id') { - result = a.id!.compareTo(b.id!); - } else { - result = compareTasks(sortColumn)(a, b); - } - return ascending ? result : -result; - }); - searchedTasks = queriedTasks; - var searchTerm = searchController.text; - if (searchVisible) { - searchedTasks = searchedTasks - .where((task) => - task.description.contains(searchTerm) || - (task.annotations?.asList() ?? []).any( - (annotation) => annotation.description.contains(searchTerm))) - .toList(); - } - pendingTags = _pendingTags(); - projects = _projects(); - } - - Map _pendingTags() { - var frequency = tagFrequencies(storage.data.pendingData()); - var modified = tagsLastModified(storage.data.pendingData()); - var setOfTags = tagSet(storage.data.pendingData()); - return SplayTreeMap.of({ - for (var tag in setOfTags) - tag: TagMetadata( - frequency: frequency[tag] ?? 0, - lastModified: modified[tag]!, - selected: selectedTags - .map( - (filter) => filter.substring(1), - ) - .contains(tag), - ), - }); - } - - Map _projects() { - var frequencies = {}; - for (var task in storage.data.pendingData()) { - if (task.project != null) { - if (frequencies.containsKey(task.project)) { - frequencies[task.project!] = (frequencies[task.project] ?? 0) + 1; - } else { - frequencies[task.project!] = 1; - } - } - } - return SplayTreeMap.of(sparseDecoratedProjectTree(frequencies)); - } - - void togglePendingFilter() { - Query(storage.tabs.tab()).togglePendingFilter(); - pendingFilter = Query(storage.tabs.tab()).getPendingFilter(); - _refreshTasks(); - setState(() {}); - } - - void toggleWaitingFilter() { - Query(storage.tabs.tab()).toggleWaitingFilter(); - waitingFilter = Query(storage.tabs.tab()).getWaitingFilter(); - _refreshTasks(); - setState(() {}); - } - - void toggleProjectFilter(String project) { - Query(storage.tabs.tab()).toggleProjectFilter(project); - projectFilter = Query(storage.tabs.tab()).projectFilter(); - _refreshTasks(); - setState(() {}); - } - - void toggleTagUnion() { - Query(storage.tabs.tab()).toggleTagUnion(); - tagUnion = Query(storage.tabs.tab()).tagUnion(); - _refreshTasks(); - setState(() {}); - } - - void selectSort(String sort) { - Query(storage.tabs.tab()).setSelectedSort(sort); - selectedSort = Query(storage.tabs.tab()).getSelectedSort(); - _refreshTasks(); - setState(() {}); - } - - void toggleTagFilter(String tag) { - if (selectedTags.contains('+$tag')) { - selectedTags - ..remove('+$tag') - ..add('-$tag'); - } else if (selectedTags.contains('-$tag')) { - selectedTags.remove('-$tag'); - } else { - selectedTags.add('+$tag'); - } - Query(storage.tabs.tab()).toggleTagFilter(tag); - selectedTags = Query(storage.tabs.tab()).getSelectedTags(); - _refreshTasks(); - setState(() {}); - } - - Task getTask(String uuid) { - return storage.data.getTask(uuid); - } - - void mergeTask(Task task) { - storage.data.mergeTask(task); - - _refreshTasks(); - setState(() {}); - } - - Future synchronize(BuildContext context, bool isDialogNeeded) async { - try { - final connectivityResult = await Connectivity().checkConnectivity(); - if (connectivityResult == ConnectivityResult.none) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - 'You are not connected to the internet. Please check your network connection.', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightPrimaryTextColor, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } else { - if (isDialogNeeded) { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return Dialog( - elevation: 5, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), - ), - child: Container( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 16.0), - Text( - "Syncing", - style: GoogleFonts.poppins( - fontSize: TaskWarriorFonts.fontSizeLarge, - fontWeight: TaskWarriorFonts.bold, - ), - ), - const SizedBox(height: 8.0), - Text( - "Please wait...", - style: GoogleFonts.poppins( - fontSize: TaskWarriorFonts.fontSizeSmall, - fontWeight: TaskWarriorFonts.regular, - ), - ), - ], - ), - ), - ); - }, - ); - } - - var header = await storage.home.synchronize(await client()); - _refreshTasks(); - pendingTags = _pendingTags(); - projects = _projects(); - setState(() {}); - - if (isDialogNeeded) { - Get.back(); - } - - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - '${header['code']}: ${header['status']}', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightPrimaryTextColor, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } - } catch (e, trace) { - if (isDialogNeeded) { - Get.back(); - } - logError(e, trace); - } - } - - void toggleSortHeader() { - sortHeaderVisible = !sortHeaderVisible; - setState(() {}); - } - - void toggleSearch() { - searchVisible = !searchVisible; - if (!searchVisible) { - searchedTasks = queriedTasks; - searchController.text = ''; - } - setState(() {}); - } - - void search(String term) { - searchedTasks = queriedTasks - .where( - (task) => task.description.toLowerCase().contains(term.toLowerCase()), - ) - .toList(); - setState(() {}); - } - - void setInitialTabIndex(int index) { - storage.tabs.setInitialTabIndex(index); - pendingFilter = Query(storage.tabs.tab()).getPendingFilter(); - waitingFilter = Query(storage.tabs.tab()).getWaitingFilter(); - selectedSort = Query(storage.tabs.tab()).getSelectedSort(); - selectedTags = Query(storage.tabs.tab()).getSelectedTags(); - projectFilter = Query(storage.tabs.tab()).projectFilter(); - _refreshTasks(); - setState(() {}); - } - - void addTab() { - storage.tabs.addTab(); - setState(() {}); - } - - List tabUuids() { - return storage.tabs.tabUuids(); - } - - int initialTabIndex() { - return storage.tabs.initialTabIndex(); - } - - void removeTab(int index) { - storage.tabs.removeTab(index); - pendingFilter = Query(storage.tabs.tab()).getPendingFilter(); - waitingFilter = Query(storage.tabs.tab()).getWaitingFilter(); - selectedSort = Query(storage.tabs.tab()).getSelectedSort(); - selectedTags = Query(storage.tabs.tab()).getSelectedTags(); - _refreshTasks(); - setState(() {}); - } - - void renameTab({ - required String tab, - required String name, - }) { - storage.tabs.renameTab(tab: tab, name: name); - setState(() {}); - } - - String? tabAlias(String tabUuid) { - return storage.tabs.alias(tabUuid); - } - - @override - Widget build(BuildContext context) { - return InheritedStorage( - storage: storage, - tasks: searchedTasks, - pendingTags: pendingTags, - projects: projects, - pendingFilter: pendingFilter, - waitingFilter: waitingFilter, - projectFilter: projectFilter, - tagUnion: tagUnion, - selectedSort: selectedSort, - getTask: getTask, - mergeTask: mergeTask, - synchronize: synchronize, - togglePendingFilter: togglePendingFilter, - toggleWaitingFilter: toggleWaitingFilter, - toggleProjectFilter: toggleProjectFilter, - toggleTagUnion: toggleTagUnion, - selectSort: selectSort, - toggleTagFilter: toggleTagFilter, - selectedTags: selectedTags, - sortHeaderVisible: sortHeaderVisible, - searchVisible: searchVisible, - toggleSortHeader: toggleSortHeader, - toggleSearch: toggleSearch, - search: search, - searchController: searchController, - setInitialTabIndex: setInitialTabIndex, - addTab: addTab, - tabUuids: tabUuids, - initialTabIndex: initialTabIndex, - removeTab: removeTab, - renameTab: renameTab, - tabAlias: tabAlias, - serverCertExists: serverCertExists, - child: widget.child, - ); - } -} - -class InheritedStorage extends InheritedModel { - // ignore: use_super_parameters - const InheritedStorage({ - super.key, - required this.storage, - required this.tasks, - required this.pendingTags, - required this.projects, - required this.pendingFilter, - required this.waitingFilter, - required this.projectFilter, - required this.tagUnion, - required this.selectedSort, - required this.selectedTags, - required this.getTask, - required this.mergeTask, - required this.synchronize, - required this.togglePendingFilter, - required this.toggleWaitingFilter, - required this.toggleProjectFilter, - required this.toggleTagUnion, - required this.toggleTagFilter, - required this.selectSort, - required this.sortHeaderVisible, - required this.searchVisible, - required this.toggleSortHeader, - required this.toggleSearch, - required this.search, - required this.searchController, - required this.setInitialTabIndex, - required this.addTab, - required this.tabUuids, - required this.initialTabIndex, - required this.removeTab, - required this.renameTab, - required this.tabAlias, - required this.serverCertExists, - required child, - }) : super(child: child); - - final Storage storage; - final List tasks; - final Map pendingTags; - final Map projects; - final bool pendingFilter; - final bool waitingFilter; - final String projectFilter; - final bool tagUnion; - final String selectedSort; - final Set selectedTags; - final Task Function(String) getTask; - final void Function(Task) mergeTask; - final void Function(BuildContext, bool) synchronize; - final void Function() togglePendingFilter; - final void Function() toggleWaitingFilter; - final void Function(String) toggleProjectFilter; - final void Function() toggleTagUnion; - final void Function(String) selectSort; - final void Function(String) toggleTagFilter; - final bool sortHeaderVisible; - final bool searchVisible; - final void Function() toggleSortHeader; - final void Function(int) setInitialTabIndex; - final void Function() addTab; - final List Function() tabUuids; - final int Function() initialTabIndex; - final void Function(int) removeTab; - final String? Function(String) tabAlias; - final void Function() toggleSearch; - final void Function(String) search; - final TextEditingController searchController; - final void Function({required String tab, required String name}) renameTab; - final bool serverCertExists; - - @override - bool updateShouldNotify(InheritedStorage oldWidget) { - return true; - } - - @override - bool updateShouldNotifyDependent( - InheritedStorage oldWidget, Set dependencies) { - return true; - } -} diff --git a/lib/routes/pageroute.dart b/lib/routes/pageroute.dart deleted file mode 100644 index 6fed2afb..00000000 --- a/lib/routes/pageroute.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:taskwarrior/views/home/home.dart'; -import 'package:taskwarrior/views/profile/profile.dart'; - -class PageRoutes { - static const String home = HomePage.routeName; - static const String profile = ProfilePage.routeName; -} diff --git a/lib/services/task_details.dart b/lib/services/task_details.dart deleted file mode 100644 index 99ba07f5..00000000 --- a/lib/services/task_details.dart +++ /dev/null @@ -1,508 +0,0 @@ -// ignore_for_file: depend_on_referenced_packages - -import 'package:flutter/material.dart'; - -import 'package:built_collection/built_collection.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:intl/intl.dart'; - -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/utility/utilities.dart'; -import 'package:taskwarrior/views/home/home.dart'; -import 'package:taskwarrior/widgets/pallete.dart'; -import 'package:taskwarrior/widgets/taskdetails.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; - -class DetailRoute extends StatefulWidget { - const DetailRoute(this.uuid, {super.key}); - - final String uuid; - - @override - State createState() => _DetailRouteState(); -} - -class _DetailRouteState extends State { - late Modify modify; - bool onedit = false; - @override - void didChangeDependencies() { - super.didChangeDependencies(); - var storageWidget = StorageWidget.of(context); - modify = Modify( - getTask: storageWidget.getTask, - mergeTask: storageWidget.mergeTask, - uuid: widget.uuid, - ); - } - - void Function(dynamic) callback(String name) { - return (newValue) { - modify.set(name, newValue); - onedit = true; - setState(() {}); - }; - } - - void saveChanges() async { - var now = DateTime.now().toUtc(); - - modify.save( - modified: () => now, - ); - onedit = true; - setState(() {}); - Navigator.of(context).pop(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - 'Task Updated', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightPrimaryTextColor, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } - - @override - Widget build(BuildContext context) { - return PopScope( - canPop: false, - onPopInvoked: (bool didPop) async { - if (didPop || !onedit) { - // print(onedit); - - await Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute(builder: (context) => const HomePage()), - (Route route) => false); - } - // ignore: use_build_context_synchronously - return showDialog( - // ignore: use_build_context_synchronously - context: context, - builder: (context) { - return Utils.showAlertDialog( - title: Text( - 'Do you want to save changes?', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - actions: [ - TextButton( - onPressed: () { - saveChanges(); - Navigator.of(context).pushNamedAndRemoveUntil( - HomePage.routeName, - (route) => false, - ); - setState(() {}); - }, - child: Text( - 'Yes', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ), - TextButton( - onPressed: () { - Navigator.of(context).pushNamedAndRemoveUntil( - HomePage.routeName, - (route) => false, - ); - }, - child: Text( - 'No', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - 'Cancel', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ), - ], - ); - }, - ); - }, - child: Scaffold( - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.kLightPrimaryBackgroundColor, - appBar: AppBar( - leading: BackButton(color: TaskWarriorColors.white), - backgroundColor: Palette.kToDark, - title: Text( - 'id: ${(modify.id == 0) ? '-' : modify.id}', - style: TextStyle( - color: TaskWarriorColors.white, - ), - ), - ), - body: Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: ListView( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 2), - children: [ - for (var entry in { - 'description': modify.draft.description, - 'status': modify.draft.status, - 'entry': modify.draft.entry, - 'modified': modify.draft.modified, - 'start': modify.draft.start, - 'end': modify.draft.end, - 'due': modify.draft.due, - 'wait': modify.draft.wait, - 'until': modify.draft.until, - 'priority': modify.draft.priority, - 'project': modify.draft.project, - 'tags': modify.draft.tags, - //'annotations': modify.draft.annotations, - //'udas': modify.draft.udas, - 'urgency': urgency(modify.draft), - //'uuid': modify.draft.uuid, - }.entries) - AttributeWidget( - name: entry.key, - value: entry.value, - callback: callback(entry.key), - ), - ], - ), - ), - floatingActionButton: (modify.changes.isEmpty) - ? null - : FloatingActionButton( - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white, - foregroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - splashColor: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white, - heroTag: "btn1", - onPressed: () { - showDialog( - context: context, - builder: (context) { - return Utils.showAlertDialog( - scrollable: true, - title: Text( - 'Review changes:', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - content: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Text( - modify.changes.entries - .map((entry) => '${entry.key}:\n' - ' old: ${entry.value['old']}\n' - ' new: ${entry.value['new']}') - .toList() - .join('\n'), - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - 'Cancel', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ), - ElevatedButton( - onPressed: () { - saveChanges(); - }, - child: Text( - 'Submit', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.black, - ), - ), - ), - ], - ); - }, - ); - }, - child: const Icon(Icons.save), - ), - ), - ); - } -} - -class AttributeWidget extends StatelessWidget { - const AttributeWidget({ - required this.name, - required this.value, - required this.callback, - super.key, - }); - - final String name; - final dynamic value; - final void Function(dynamic) callback; - - @override - Widget build(BuildContext context) { - var localValue = (value is DateTime) - ? DateFormat.yMEd().add_jms().format(value.toLocal()) - : ((value is BuiltList) ? (value).toBuilder() : value); - - switch (name) { - case 'description': - return DescriptionWidget( - name: name, - value: localValue, - callback: callback, - ); - case 'status': - return StatusWidget( - name: name, - value: localValue, - callback: callback, - ); - case 'start': - return StartWidget( - name: name, - value: localValue, - callback: callback, - ); - case 'due': - return DateTimeWidget( - name: name, - value: localValue, - callback: callback, - ); - case 'wait': - return DateTimeWidget( - name: name, - value: localValue, - callback: callback, - ); - case 'until': - return DateTimeWidget( - name: name, - value: localValue, - callback: callback, - ); - case 'priority': - return PriorityWidget( - name: name, - value: localValue, - callback: callback, - ); - case 'project': - return ProjectWidget( - name: name, - value: localValue, - callback: callback, - ); - case 'tags': - return TagsWidget( - name: name, - value: localValue, - callback: callback, - ); - // case 'annotations': - // return AnnotationsWidget( - // name: name, - // value: localValue, - // callback: callback, - // ); - default: - return Card( - color: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - child: ListTile( - textColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightSecondaryTextColor, - title: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - Text( - '$name:'.padRight(13), - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - Text( - localValue?.toString() ?? "not selected", - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ], - ), - ), - ), - ); - } - } -} - -class TagsWidget extends StatelessWidget { - const TagsWidget({ - required this.name, - required this.value, - required this.callback, - super.key, - }); - - final String name; - final dynamic value; - final void Function(dynamic) callback; - @override - Widget build(BuildContext context) { - return Card( - color: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - child: ListTile( - textColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.ksecondaryTextColor, - title: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - RichText( - text: TextSpan( - children: [ - TextSpan( - text: '$name:'.padRight(13), - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - TextSpan( - text: - '${(value as ListBuilder?)?.build() ?? 'not selected'}', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ], - ), - ), - // Text( - // '${'$name: '}${(value as ListBuilder?)?.build()}', - // style: GoogleFonts.poppins(), - // ), - ], - ), - ), - onTap: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => TagsRoute( - value: value, - callback: callback, - ), - ), - ), - ), - ); - } -} -// class AnnotationsWidget extends StatelessWidget { -// const AnnotationsWidget({ -// required this.name, -// required this.value, -// required this.callback, -// super.key, -// }); - -// final String name; -// final dynamic value; -// final void Function(dynamic) callback; - -// @override -// Widget build(BuildContext context) { -// return Card( -// child: ListTile( -// title: SingleChildScrollView( -// scrollDirection: Axis.horizontal, -// child: Row( -// children: [ -// Text( -// (value == null) -// ? '${'$name:'.padRight(13)}null' -// : '${'$name:'.padRight(13)}${(value as ListBuilder).length} annotation(s)', -// style: GoogleFonts.poppins(), -// ), -// ], -// ), -// ), -// onTap: () => Navigator.push( -// context, -// MaterialPageRoute( -// builder: (context) => AnnotationsRoute( -// value: value, -// callback: callback, -// ), -// ), -// ), -// ), -// ); -// } -// } \ No newline at end of file diff --git a/lib/services/task_list_tem.dart b/lib/services/task_list_tem.dart deleted file mode 100644 index 97ceade6..00000000 --- a/lib/services/task_list_tem.dart +++ /dev/null @@ -1,210 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/model/json.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; - -class TaskListItem extends StatefulWidget { - const TaskListItem(this.task, {this.pendingFilter = false,this.waitingFilter = false, super.key, required this.darkmode}); - - final Task task; - final bool pendingFilter; - final bool waitingFilter; - final bool darkmode; - - @override - State createState() => _TaskListItemState(); -} - -class _TaskListItemState extends State { - late Modify modify; - bool isChecked = false; - bool useDelayTask = false; // Default value - - @override - void initState() { - super.initState(); - loadDelayTask(); - } - - Future loadDelayTask() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - setState(() { - useDelayTask = prefs.getBool('delaytask') ?? false; - }); - } - - bool isDueWithinOneDay(DateTime dueDate) { - DateTime now = DateTime.now(); - Duration difference = dueDate.difference(now); - return difference.inDays <= 1 && difference.inDays >= 0; - } - - void saveChanges() async { - var now = DateTime.now().toUtc(); - modify.save( - modified: () => now, - ); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - 'Task Updated', - style: TextStyle( - color: - AppSettings.isDarkMode ? TaskWarriorColors.kprimaryTextColor : TaskWarriorColors.kLightPrimaryTextColor, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } - - @override - Widget build(BuildContext context) { - MaterialColor colours = Colors.grey; - var colour = widget.darkmode ? Colors.white : Colors.black; - var dimColor = widget.darkmode ? const Color.fromARGB(137, 248, 248, 248) : const Color.fromARGB(136, 17, 17, 17); - - if (widget.task.priority == 'H') { - colours = Colors.red; - } else if (widget.task.priority == 'M') { - colours = Colors.yellow; - } else if (widget.task.priority == 'L') { - colours = Colors.green; - } - - if ((widget.task.status[0].toUpperCase()) == 'P') { - // Pending tasks - return Container( - decoration: BoxDecoration( - border: Border.all( - color: (widget.task.due != null && isDueWithinOneDay(widget.task.due!) && useDelayTask) - ? Colors.red // Set border color to red if due within 1 day and useDelayTask is true - : dimColor, // Set default border color - ), - borderRadius: BorderRadius.circular(8.0), - ), - child: ListTile( - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - CircleAvatar( - backgroundColor: colours, - radius: 8, - ), - const SizedBox(width: 8), - SizedBox( - width: MediaQuery.of(context).size.width * 0.70, - child: Text( - '${(widget.task.id == 0) ? '#' : widget.task.id}. ${widget.task.description}', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: GoogleFonts.poppins( - color: colour, - ), - ), - ), - ], - ), - Text( - (widget.task.annotations != null) ? ' [${widget.task.annotations!.length}]' : '', - style: GoogleFonts.poppins( - color: colour, - ), - ), - ], - ), - subtitle: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Text( - '${widget.pendingFilter ? '' : '${widget.task.status[0].toUpperCase()}\n'}' - 'Last Modified: ${(widget.task.modified != null) ? age(widget.task.modified!) : ((widget.task.start != null) ? age(widget.task.start!) : '-')} | ' - 'Due: ${(widget.task.due != null) ? when(widget.task.due!) : '-'}' - .replaceFirst(RegExp(r' \[\]$'), '') - .replaceAll(RegExp(r' +'), ' '), - overflow: TextOverflow.ellipsis, - style: GoogleFonts.poppins( - color: dimColor, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - ), - ), - ), - Text( - formatUrgency(urgency(widget.task)), - style: GoogleFonts.poppins( - color: colour, - ), - ), - ], - ), - ), - ); - } else { - // Completed tasks - return ListTile( - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - CircleAvatar( - backgroundColor: colours, - radius: 8, - ), - const SizedBox(width: 8), - SizedBox( - width: MediaQuery.of(context).size.width * 0.65, - child: Text( - '${(widget.task.id == 0) ? '#' : widget.task.id}. ${widget.task.description}', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: GoogleFonts.poppins( - color: colour, - ), - ), - ), - ], - ), - ], - ), - subtitle: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Text( - 'Last Modified: ${(widget.task.modified != null) ? age(widget.task.modified!) : ((widget.task.start != null) ? age(widget.task.start!) : '-')} | ' - 'Due: ${(widget.task.due != null) ? when(widget.task.due!) : '-'}' - .replaceFirst(RegExp(r' \[\]$'), '') - .replaceAll(RegExp(r' +'), ' '), - overflow: TextOverflow.ellipsis, - style: GoogleFonts.poppins( - color: dimColor, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - ), - ), - ), - Text( - formatUrgency(urgency(widget.task)), - style: GoogleFonts.poppins( - color: colour, - ), - ), - ], - ), - ); - } - } -} diff --git a/lib/taskserver/configure_taskserver.dart b/lib/taskserver/configure_taskserver.dart deleted file mode 100644 index 7401b334..00000000 --- a/lib/taskserver/configure_taskserver.dart +++ /dev/null @@ -1,731 +0,0 @@ -// // ignore_for_file: use_build_context_synchronously - -// import 'dart:math'; -// import 'package:flutter/foundation.dart'; -// import 'package:flutter/material.dart'; -// import 'package:flutter/services.dart'; -// import 'package:google_fonts/google_fonts.dart'; -// import 'package:loggy/loggy.dart'; -// import 'package:shared_preferences/shared_preferences.dart'; -// import 'package:taskwarrior/config/app_settings.dart'; -// import 'package:taskwarrior/model/storage.dart'; -// import 'package:taskwarrior/model/storage/client.dart'; -// import 'package:taskwarrior/model/storage/set_config.dart'; -// import 'package:taskwarrior/model/storage/storage_widget.dart'; -// import 'package:taskwarrior/widgets/fingerprint.dart'; -// import 'package:taskwarrior/widgets/home_paths.dart' as rc; -// import 'package:taskwarrior/widgets/pallete.dart'; -// import 'package:taskwarrior/widgets/taskdetails.dart'; -// import 'package:taskwarrior/widgets/taskserver.dart'; - - - - - -// ///Old code for the configure taskserver screen -// ///we will keep it till the stable release - - - - - -// class ConfigureTaskserverRoute extends StatefulWidget { -// const ConfigureTaskserverRoute([Key? key]) : super(key: key); - -// @override -// State createState() => -// _ConfigureTaskserverRouteState(); -// } - -// class _ConfigureTaskserverRouteState extends State { -// late Storage storage; -// Server? server; -// Credentials? credentials; -// bool _value = false; -// @override -// void initState() { -// super.initState(); -// checkAutoSync(); -// } - -// checkAutoSync() async { -// ///check if auto sync is on or off -// final SharedPreferences prefs = await SharedPreferences.getInstance(); -// setState(() { -// _value = prefs.getBool('sync') ?? false; -// }); -// } - -// @override -// void didChangeDependencies() { -// super.didChangeDependencies(); -// storage = StorageWidget.of(context).storage; -// } - -// Future _setConfigurationFromFixtureForDebugging() async { -// var contents = await rootBundle.loadString('assets/.taskrc'); -// rc.Taskrc(storage.home.home).addTaskrc(contents); -// server = Taskrc.fromString(contents).server; -// credentials = Taskrc.fromString(contents).credentials; -// for (var entry in { -// 'taskd.certificate': '.task/first_last.cert.pem', -// 'taskd.key': '.task/first_last.key.pem', -// 'taskd.ca': '.task/ca.cert.pem', -// // 'server.cert': '.task/server.cert.pem', -// }.entries) { -// var contents = await rootBundle.loadString('assets/${entry.value}'); -// storage.guiPemFiles.addPemFile( -// key: entry.key, -// contents: contents, -// name: entry.value.split('/').last, -// ); -// } -// setState(() {}); -// } - -// ///fetch statistics from the taskserver -// Future _showStatistics(BuildContext context) async { -// ///show loading dialog -// ///while the statistics are being fetched -// showDialog( -// context: context, -// barrierDismissible: false, -// builder: (context) { -// return const AlertDialog( -// title: Text('Fetching statistics...'), -// content: Column( -// mainAxisSize: MainAxisSize.min, -// children: [ -// CircularProgressIndicator(), -// SizedBox(height: 16), -// Text('Please wait...'), -// ], -// ), -// ); -// }, -// ); - -// try { -// // Fetch statistics header from storage -// var header = await storage.home.statistics(await client()); - -// // Determine the maximum key length for formatting purposes -// var maxKeyLength = -// header.keys.map((key) => (key as String).length).reduce(max); - -// // Dismiss the loading dialog -// Navigator.of(context).pop(); - -// // Show statistics in a dialog -// await showDialog( -// context: context, -// builder: (context) => AlertDialog( -// scrollable: true, -// title: const Text('Statistics:'), -// content: SingleChildScrollView( -// scrollDirection: Axis.horizontal, -// child: Row( -// children: [ -// Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// // Display each key-value pair in the statistics header -// for (var key in header.keys.toList()) -// Text( -// '${'$key:'.padRight(maxKeyLength + 1)} ${header[key]}', -// style: GoogleFonts.poppins(), -// ), -// ], -// ), -// ], -// ), -// ), -// actions: [ -// ElevatedButton( -// onPressed: () { -// Navigator.of(context).pop(); -// }, -// child: const Text('Ok'), -// ), -// ], -// ), -// ); -// } on Exception catch (e, trace) { -// // Dismiss the loading dialog -// Navigator.of(context).pop(); - -// // Log the error and trace -// logError(e, trace); - -// // Refresh the state of ProfilesWidget -// ProfilesWidget.of(context).setState(() {}); -// } -// } - -// @override -// Widget build(BuildContext context) { -// var profile = storage.profile.uri.pathSegments.lastWhere( -// (segment) => segment.isNotEmpty, -// ); -// var alias = ProfilesWidget.of(context).profilesMap[profile]; - -// var contents = rc.Taskrc(storage.home.home).readTaskrc(); -// if (contents != null) { -// server = Taskrc.fromString(contents).server; -// credentials = Taskrc.fromString(contents).credentials; -// } -// var color = -// AppSettings.isDarkMode ? Colors.white : Palette.kToDark.shade200; -// return Scaffold( -// appBar: AppBar( -// backgroundColor: Palette.kToDark.shade200, -// titleSpacing: -// 0, // Reduce the spacing between the title and leading/back button -// title: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// const Text( -// "Configure TaskServer", -// style: GoogleFonts.poppins(color: Colors.white, fontSize: 18), -// ), -// Text( -// alias ?? profile, -// style: const GoogleFonts.poppins(color: Colors.white, fontSize: 12), -// ), -// ], -// ), -// actions: [ -// if (kDebugMode) -// IconButton( -// icon: const Icon( -// Icons.bug_report, -// color: Colors.white, -// ), -// onPressed: _setConfigurationFromFixtureForDebugging, -// ), -// IconButton( -// icon: const Icon( -// Icons.show_chart, -// color: Colors.white, -// ), -// onPressed: () => _showStatistics(context), -// ), -// ], -// leading: const BackButton( -// color: Colors.white, -// ), -// ), -// body: Container( -// color: AppSettings.isDarkMode ? Palette.kToDark.shade200 : Colors.white, -// child: ListView( -// children: [ -// TaskrcWidget(storage), -// for (var pem in [ -// 'taskd.certificate', -// 'taskd.key', -// 'taskd.ca', -// if (StorageWidget.of(context).serverCertExists) 'server.cert', -// ]) -// PemWidget( -// storage: storage, -// pem: pem, -// ), -// const SizedBox(height: 16), -// Row( -// mainAxisAlignment: MainAxisAlignment.center, -// children: [ -// Icon(Icons.info_outline, color: color), -// const SizedBox(width: 8), -// InkWell( -// onTap: () { -// ///Link the TaskServer or the Inther.am documentation -// }, -// child: SizedBox( -// width: MediaQuery.of(context).size.width * 0.8, -// child: Text( -// "I dont know how to configure the TaskServer", -// textAlign: TextAlign.center, -// maxLines: 2, -// style: GoogleFonts.poppins( -// color: color, -// decoration: TextDecoration.underline, -// fontWeight: FontWeight.w500, -// ), -// ), -// ), -// ) -// ], -// ), -// Divider( -// height: MediaQuery.of(context).size.height * 0.05, -// ), -// Padding( -// padding: const EdgeInsets.only(left: 20, right: 20), -// child: Row( -// children: [ -// Text( -// 'TaskServer Sync Configuration', -// textAlign: TextAlign.center, -// maxLines: 2, -// style: GoogleFonts.poppins( -// color: color, -// decoration: TextDecoration.underline, -// fontWeight: FontWeight.w800, -// ), -// ), -// ], -// ), -// ), -// SizedBox( -// height: MediaQuery.of(context).size.height * 0.02, -// ), -// Padding( -// padding: const EdgeInsets.only(left: 20, right: 20), -// child: Row( -// children: [ -// Text( -// "Auto Sync", -// textAlign: TextAlign.center, -// maxLines: 2, -// style: GoogleFonts.poppins( -// color: color, -// decoration: TextDecoration.underline, -// fontWeight: FontWeight.w500, -// ), -// ), -// const SizedBox( -// width: 20, -// ), -// Switch( -// splashRadius: 24, -// // inactiveTrackColor: Colors.pink, -// // activeTrackColor: Colors.green, -// thumbIcon: MaterialStateProperty.resolveWith( -// (Set states) { -// if (states.contains(MaterialState.selected)) { -// return const Icon(Icons.check, color: Colors.green); -// } -// return null; -// }, -// ), -// // activeColor: Colors.lime, -// // inactiveThumbColor: Colors.orange, -// value: _value, -// onChanged: (bool value) async { -// setState(() { -// _value = !_value; -// }); - -// final SharedPreferences prefs = -// await SharedPreferences.getInstance(); -// await prefs.setBool('sync', _value); -// }), -// ], -// ), -// ), -// SizedBox( -// height: MediaQuery.of(context).size.height * 0.02, -// ), -// Padding( -// padding: const EdgeInsets.only( -// left: 20, -// ), -// child: Row( -// children: [ -// SizedBox( -// width: MediaQuery.of(context).size.width * 0.9, -// child: Text( -// "Sync tasks from taskserver automatically on initialization", -// textAlign: TextAlign.start, -// maxLines: 2, -// style: GoogleFonts.poppins( -// color: color, -// decoration: TextDecoration.underline, -// fontWeight: FontWeight.w400, -// ), -// ), -// ), -// ], -// ), -// ), -// ], -// ), -// ), -// ); -// } -// } - -// class PemWidget extends StatefulWidget { -// const PemWidget({required this.storage, required this.pem, Key? key}) -// : super(key: key); - -// final Storage storage; -// final String pem; - -// @override -// State createState() => _PemWidgetState(); -// } - -// class _PemWidgetState extends State { -// @override -// Widget build(BuildContext context) { -// var color = -// AppSettings.isDarkMode ? Colors.white : Palette.kToDark.shade200; -// var contents = widget.storage.guiPemFiles.pemContents(widget.pem); -// var name = widget.storage.guiPemFiles.pemFilename(widget.pem); -// return ListTile( -// textColor: color, -// title: SingleChildScrollView( -// scrollDirection: Axis.horizontal, - -// child: RichText( -// text: TextSpan( -// children: [ -// TextSpan( -// text: widget.pem.padRight(17), -// style: GoogleFonts.poppins( -// color: color, -// fontWeight: FontWeight.w800, -// ), -// ), -// name == null -// ? TextSpan( -// text: " = Not Configured", -// style: GoogleFonts.poppins( -// color: color, -// fontWeight: FontWeight.w400, -// ), -// ) -// : TextSpan( -// text: (widget.pem == 'server.cert') ? '' : ' = $name', -// style: GoogleFonts.poppins( -// color: color, -// fontWeight: FontWeight.w400, -// ), -// ) -// ], -// ), -// ), -// // child: Text( -// // '${widget.pem.padRight(17)}${(widget.pem == 'server.cert') ? '' : ' = $name'}', -// // style: GoogleFonts.poppins(), -// // ), -// ), -// subtitle: (key) { -// if (key == 'taskd.key') { -// return null; -// } -// if (contents == null) { -// return SingleChildScrollView( -// scrollDirection: Axis.horizontal, -// child: Text( -// 'SHA1: null', -// style: GoogleFonts.poppins( -// color: color, -// fontWeight: FontWeight.w800, -// ), -// ), -// ); -// } -// try { -// var identifier = fingerprint(contents).toUpperCase(); -// return SingleChildScrollView( -// scrollDirection: Axis.horizontal, -// child: RichText( -// text: TextSpan( -// children: [ -// TextSpan( -// text: 'SHA1 = ', -// style: GoogleFonts.poppins( -// color: color, -// fontWeight: FontWeight.w800, -// ), -// ), -// TextSpan( -// text: identifier, -// style: GoogleFonts.poppins( -// color: color, -// fontWeight: FontWeight.w400, -// ), -// ) -// ], -// ), -// ), -// ); -// // ignore: avoid_catches_without_on_clauses -// } catch (e) { -// return SingleChildScrollView( -// scrollDirection: Axis.horizontal, -// child: Text( -// '${e.runtimeType}', -// style: GoogleFonts.poppins(), -// ), -// ); -// } -// }(widget.pem), -// onTap: (widget.pem == 'server.cert') -// ? () { -// widget.storage.guiPemFiles.removeServerCert(); -// ProfilesWidget.of(context).setState(() {}); -// setState(() {}); -// } -// : () async { -// await setConfig(storage: widget.storage, key: widget.pem); -// setState(() {}); -// }, -// onLongPress: (widget.pem != 'server.cert' && name != null) -// ? () { -// widget.storage.guiPemFiles.removePemFile(widget.pem); -// setState(() {}); -// } -// : null, -// ); -// } -// } - -// class TaskrcWidget extends StatefulWidget { -// const TaskrcWidget(this.storage, {Key? key}) : super(key: key); - -// final Storage storage; - -// @override -// State createState() => _TaskrcWidgetState(); -// } - -// class _TaskrcWidgetState extends State { -// final TextEditingController _taskrcContentController = -// TextEditingController(); -// bool hideKey = true; - -// Storage get storage => widget.storage; - -// @override -// Widget build(BuildContext context) { -// var color = -// AppSettings.isDarkMode ? Colors.white : Palette.kToDark.shade200; - -// Server? server; -// Credentials? credentials; - -// /// The [setConfig] function is used to set the configuration of the TaskServer from the clipboard -// void setContent() async { -// final clipboardData = await Clipboard.getData(Clipboard.kTextPlain); -// _taskrcContentController.text = clipboardData?.text ?? ''; - -// // Check if the clipboard data is not empty -// if (_taskrcContentController.text.isNotEmpty) { -// // Add the clipboard data to the taskrc file -// storage.taskrc.addTaskrc(_taskrcContentController.text); - -// // Read the contents of the taskrc file -// var contents = rc.Taskrc(widget.storage.home.home).readTaskrc(); - -// // Check if the contents were successfully read -// if (contents != null) { -// // Parse the contents into a Taskrc object -// var taskrc = Taskrc.fromString(contents); - -// // Check if the server and credentials are present in the Taskrc object -// if (taskrc.server != null && taskrc.credentials != null) { -// // Update the server and credentials variables -// setState(() { -// server = taskrc.server; -// credentials = taskrc.credentials; -// }); -// } else { -// // Handle the case when server or credentials are missing in the Taskrc object -// ScaffoldMessenger.of(context).showSnackBar( -// const SnackBar( -// content: Text( -// 'Error: Server or credentials missing in taskrc file')), -// ); -// } -// } else { -// // Handle the case when there is an error reading the taskrc file -// ScaffoldMessenger.of(context).showSnackBar( -// const SnackBar(content: Text('Error: Failed to read taskrc file')), -// ); -// } -// } -// } - -// var contents = rc.Taskrc(widget.storage.home.home).readTaskrc(); -// if (contents != null) { -// server = Taskrc.fromString(contents).server; -// credentials = Taskrc.fromString(contents).credentials; -// } -// String? credentialsString; -// if (credentials != null) { -// String key; -// if (hideKey) { -// key = credentials!.key.replaceAll(RegExp(r'[0-9a-f]'), '*'); -// } else { -// key = credentials!.key; -// } - -// credentialsString = '${credentials!.org}/${credentials!.user}/$key'; - -// if (credentialsString.isNotEmpty && server.toString().isNotEmpty) { -// //print(credentialsString); -// _taskrcContentController.text = -// "taskd.server=$server\ntaskd.credentials=${credentials!.org}/${credentials!.user}/$key"; -// } -// } -// var height = MediaQuery.of(context).size.height; -// return Column( -// children: [ -// Padding( -// padding: const EdgeInsets.all(12.0), -// child: SizedBox( -// height: height * 0.15, -// child: TextField( -// controller: _taskrcContentController, -// maxLines: 8, -// style: const GoogleFonts.poppins(color: Colors.white), -// decoration: InputDecoration( -// suffixIconConstraints: const BoxConstraints( -// maxHeight: 24, -// maxWidth: 24, -// ), -// isDense: true, -// suffix: IconButton( -// onPressed: () async { -// setContent(); -// }, -// icon: const Icon(Icons.content_paste)), -// border: const OutlineInputBorder(), -// labelStyle: GoogleFonts.poppins(color: color), -// labelText: 'Paste your taskrc contents here', -// ), -// ), -// ), -// ), -// Row( -// mainAxisAlignment: MainAxisAlignment.center, -// children: [ -// Text( -// "Or", -// style: GoogleFonts.poppins(color: color), -// ) -// ], -// ), -// FilledButton.tonal( -// onPressed: () async { -// await setConfig( -// storage: widget.storage, -// key: 'TASKRC', -// ); -// setState(() {}); -// }, -// child: const Text('Select TASKRC'), -// ), -// Divider( -// height: height * 0.05, -// ), -// ListTile( -// textColor: color, -// title: SingleChildScrollView( -// scrollDirection: Axis.horizontal, -// child: Text( -// 'Selected TaskServer Configuration', -// style: GoogleFonts.poppins(fontWeight: FontWeight.w800), -// ), -// ), -// ), -// ListTile( -// textColor: color, -// title: SingleChildScrollView( -// scrollDirection: Axis.horizontal, -// child: RichText( -// text: TextSpan( -// children: [ -// TextSpan( -// text: 'taskd.server = ', -// style: GoogleFonts.poppins( -// color: color, -// fontWeight: FontWeight.w800, -// ), -// ), -// server == null -// ? TextSpan( -// text: 'Not Configured', -// style: GoogleFonts.poppins( -// color: color, -// fontWeight: FontWeight.w400, -// ), -// ) -// : TextSpan( -// text: '$server', -// style: GoogleFonts.poppins( -// color: color, -// fontWeight: FontWeight.w400, -// ), -// ) -// ], -// ), -// ), -// ), -// onTap: (server == null) -// ? null -// : () async { -// late String mainDomain; -// if (server!.address == 'localhost') { -// mainDomain = server!.address; -// } else { -// var parts = server!.address.split('.'); -// var length = parts.length; -// mainDomain = parts.sublist(length - 2, length).join('.'); -// } - -// ProfilesWidget.of(context).renameProfile( -// profile: widget.storage.profile.path.split('/').last, -// alias: mainDomain, -// ); -// }), -// ListTile( -// textColor: color, -// title: SingleChildScrollView( -// scrollDirection: Axis.horizontal, -// child: RichText( -// text: TextSpan( -// children: [ -// TextSpan( -// text: 'taskd.credentials = ', -// style: GoogleFonts.poppins( -// color: color, -// fontWeight: FontWeight.w800, -// ), -// ), -// credentialsString == null -// ? TextSpan( -// text: 'Not Configured', -// style: GoogleFonts.poppins( -// color: color, -// fontWeight: FontWeight.w400, -// ), -// ) -// : TextSpan( -// text: credentialsString, -// style: GoogleFonts.poppins( -// color: color, -// fontWeight: FontWeight.w400, -// ), -// ) -// ], -// ), -// ), -// ), -// trailing: (credentials == null) -// ? null -// : IconButton( -// icon: Icon(hideKey ? Icons.visibility_off : Icons.visibility), -// onPressed: () { -// hideKey = !hideKey; -// setState(() {}); -// }, -// ), -// ), -// ], -// ); -// } -// } diff --git a/lib/taskserver/ntaskserver.dart b/lib/taskserver/ntaskserver.dart deleted file mode 100644 index 0dfc75ee..00000000 --- a/lib/taskserver/ntaskserver.dart +++ /dev/null @@ -1,995 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:loggy/loggy.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/utility/utilities.dart'; -import 'package:taskwarrior/widgets/taskdetails/profiles_widget.dart'; -// ignore_for_file: use_build_context_synchronously - -import 'dart:math'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/model/storage.dart'; -import 'package:taskwarrior/model/storage/client.dart'; -import 'package:taskwarrior/model/storage/set_config.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/widgets/fingerprint.dart'; -import 'package:taskwarrior/widgets/home_paths.dart' as rc; -import 'package:taskwarrior/widgets/taskdetails.dart'; -import 'package:taskwarrior/widgets/taskserver.dart'; -import 'package:url_launcher/url_launcher.dart'; - -class ManageTaskServer extends StatefulWidget { - const ManageTaskServer({super.key}); - - @override - State createState() => _ManageTaskServerState(); -} - -class _ManageTaskServerState extends State { - late Storage storage; - Server? server; - Credentials? credentials; - - bool isTaskDServerActive = true; - @override - void initState() { - super.initState(); - } - - bool hideKey = true; - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - storage = StorageWidget.of(context).storage; - } - - Future _setConfigurationFromFixtureForDebugging() async { - var contents = await rootBundle.loadString('assets/.taskrc'); - rc.Taskrc(storage.home.home).addTaskrc(contents); - server = Taskrc.fromString(contents).server; - credentials = Taskrc.fromString(contents).credentials; - for (var entry in { - 'taskd.certificate': '.task/first_last.cert.pem', - 'taskd.key': '.task/first_last.key.pem', - 'taskd.ca': '.task/ca.cert.pem', - // 'server.cert': '.task/server.cert.pem', - }.entries) { - var contents = await rootBundle.loadString('assets/${entry.value}'); - storage.guiPemFiles.addPemFile( - key: entry.key, - contents: contents, - name: entry.value.split('/').last, - ); - } - setState(() {}); - } - - ///fetch statistics from the taskserver - Future _showStatistics(BuildContext context) async { - ///show loading dialog - ///while the statistics are being fetched - - showDialog( - context: context, - barrierDismissible: false, - builder: (context) { - return Utils.showAlertDialog( - title: Text( - 'Fetching statistics...', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 16), - Text( - 'Please wait...', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ], - ), - ); - }, - ); - - try { - // Fetch statistics header from storage - var header = await storage.home.statistics(await client()); - - // Determine the maximum key length for formatting purposes - var maxKeyLength = - header.keys.map((key) => (key as String).length).reduce(max); - - // Dismiss the loading dialog - Navigator.of(context).pop(); - - // Show statistics in a dialog - await showDialog( - context: context, - builder: (context) => Utils.showAlertDialog( - scrollable: true, - title: Text( - 'Statistics:', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - content: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Display each key-value pair in the statistics header - for (var key in header.keys.toList()) - Text( - '${'$key:'.padRight(maxKeyLength + 1)} ${header[key]}', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ], - ), - ], - ), - ), - actions: [ - ElevatedButton( - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - AppSettings.isDarkMode - ? TaskWarriorColors.kLightSecondaryBackgroundColor - : TaskWarriorColors.ksecondaryBackgroundColor, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - 'Ok', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white, - ), - ), - ), - ], - ), - ); - } on Exception catch (e, trace) { - // Dismiss the loading dialog - Navigator.of(context).pop(); - - //Displaying Error message. - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - trace.toString().startsWith("#0") - ? "Please set up your TaskServer." - : e.toString(), - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightPrimaryTextColor, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - // Log the error and trace - logError(e, trace); - // Refresh the state of ProfilesWidget - ProfilesWidget.of(context).setState(() {}); - } - } - - @override - Widget build(BuildContext context) { - var profile = storage.profile.uri.pathSegments.lastWhere( - (segment) => segment.isNotEmpty, - ); - final TextEditingController taskrcContentController = - TextEditingController(); - Server? server; - Credentials? credentials; - - var alias = ProfilesWidget.of(context).profilesMap[profile]; - - var contents = rc.Taskrc(storage.home.home).readTaskrc(); - if (contents != null) { - server = Taskrc.fromString(contents).server; - credentials = Taskrc.fromString(contents).credentials; - } - var tileColor = AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor; - if (contents != null) { - server = Taskrc.fromString(contents).server; - credentials = Taskrc.fromString(contents).credentials; - } - - String? credentialsString; - if (credentials != null) { - String key; - if (hideKey) { - key = credentials.key.replaceAll(RegExp(r'[0-9a-f]'), '*'); - } else { - key = credentials.key; - } - - credentialsString = '${credentials.org}/${credentials.user}/$key'; - - if (credentialsString.isNotEmpty && server.toString().isNotEmpty) { - //print(credentialsString); - taskrcContentController.text = - "taskd.server=$server\ntaskd.credentials=${credentials.org}/${credentials.user}/$key"; - - setState(() { - isTaskDServerActive = false; - }); - } - } - - /// The [setConfig] function is used to set the configuration of the TaskServer from the clipboard - void setContent() async { - final clipboardData = await Clipboard.getData(Clipboard.kTextPlain); - taskrcContentController.text = clipboardData?.text ?? ''; - - // Check if the clipboard data is not empty - if (taskrcContentController.text.isNotEmpty) { - // Add the clipboard data to the taskrc file - storage.taskrc.addTaskrc(taskrcContentController.text); - - // Read the contents of the taskrc file - var contents = rc.Taskrc(storage.home.home).readTaskrc(); - - // Check if the contents were successfully read - if (contents != null) { - // Parse the contents into a Taskrc object - var taskrc = Taskrc.fromString(contents); - - // Check if the server and credentials are present in the Taskrc object - if (taskrc.server != null && taskrc.credentials != null) { - // Update the server and credentials variables - setState(() { - server = taskrc.server; - credentials = taskrc.credentials; - }); - // Handle the case when server or credentials are missing in the Taskrc object - Navigator.pop(context); - - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - 'Success: Server or credentials are verified in taskrc file', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } else { - Navigator.pop(context); - // Handle the case when server or credentials are missing in the Taskrc object - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - 'Error: Server or credentials are missing in taskrc file', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } - } else { - Navigator.pop(context); - - // Handle the case when there is an error reading the taskrc file - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - 'Error: Failed to read taskrc file', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } - } - } - - var height = MediaQuery.of(context).size.height; - return Scaffold( - appBar: AppBar( - backgroundColor: TaskWarriorColors.kprimaryBackgroundColor, - titleSpacing: - 0, // Reduce the spacing between the title and leading/back button - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Configure TaskServer", - style: GoogleFonts.poppins( - color: TaskWarriorColors.white, - fontSize: TaskWarriorFonts.fontSizeLarge, - ), - ), - Text( - alias ?? profile, - style: GoogleFonts.poppins( - color: TaskWarriorColors.white, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - ), - ], - ), - actions: [ - IconButton( - icon: Icon( - Icons.info, - color: TaskWarriorColors.white, - ), - onPressed: () async { - String url = - "https://github.com/CCExtractor/taskwarrior-flutter?tab=readme-ov-file#taskwarrior-mobile-app"; - if (!await launchUrl(Uri.parse(url))) { - throw Exception('Could not launch $url'); - } - }, - ), - if (kDebugMode) - IconButton( - icon: Icon( - Icons.bug_report, - color: TaskWarriorColors.white, - ), - onPressed: _setConfigurationFromFixtureForDebugging, - ), - IconButton( - icon: Icon( - Icons.show_chart, - color: TaskWarriorColors.white, - ), - onPressed: () => _showStatistics(context), - ), - ], - leading: BackButton( - color: TaskWarriorColors.white, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.kLightPrimaryBackgroundColor, - body: Padding( - padding: const EdgeInsets.only(left: 20, right: 20), - child: ListView( - // mainAxisAlignment: MainAxisAlignment.start, - // crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Configure TASKRC", - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - const SizedBox(height: 10), - GestureDetector( - onTap: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) { - return StatefulBuilder( - builder: - (BuildContext context, StateSetter setState) { - // double heightOfModalBottomSheet = - // MediaQuery.of(context).size.height * 0.6; - - return Padding( - padding: MediaQuery.of(context).viewInsets, - child: Wrap( - children: [ - Container( - // height: heightOfModalBottomSheet, - padding: const EdgeInsets.all(16.0), - decoration: BoxDecoration( - color: tileColor, - borderRadius: - const BorderRadius.vertical( - top: Radius.circular(16.0)), - ), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.start, - children: [ - Text( - 'Configure TaskRc', - style: TextStyle( - fontWeight: TaskWarriorFonts.bold, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - Text( - 'Paste the taskrc content or select taskrc file', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - const SizedBox(height: 16.0), - Padding( - padding: const EdgeInsets.all(12.0), - child: SizedBox( - height: height * 0.15, - child: TextField( - style: TextStyle( - color: - AppSettings.isDarkMode - ? TaskWarriorColors - .white - : TaskWarriorColors - .black), - controller: - taskrcContentController, - maxLines: 8, - decoration: InputDecoration( - counterStyle: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors - .white - : TaskWarriorColors - .black), - suffixIconConstraints: - const BoxConstraints( - maxHeight: 24, - maxWidth: 24, - ), - isDense: true, - suffix: IconButton( - onPressed: () async { - setContent(); - }, - icon: const Icon( - Icons.content_paste), - ), - border: - const OutlineInputBorder(), - labelStyle: GoogleFonts - .poppins( - color: AppSettings - .isDarkMode - ? TaskWarriorColors - .white - : TaskWarriorColors - .black), - labelText: - 'Paste your taskrc contents here'), - ), - ), - ), - Text( - "Or", - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - FilledButton.tonal( - style: ButtonStyle( - backgroundColor: AppSettings - .isDarkMode - ? WidgetStateProperty.all< - Color>( - TaskWarriorColors.black) - : WidgetStateProperty.all< - Color>( - TaskWarriorColors - .white)), - onPressed: () async { - await setConfig( - storage: storage, - key: 'TASKRC', - ); - setState(() {}); - Get.back(); - }, - child: Text( - 'Select TASKRC file', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ), - ], - ), - ), - ], - ), - ); - }, - ); - }, - ); - }, - child: Container( - width: MediaQuery.of(context).size.width * 1, - // height: MediaQuery.of(context).size.height * 0.05, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: tileColor, - borderRadius: BorderRadius.circular(8), - border: - Border.all(color: TaskWarriorColors.borderColor), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - taskrcContentController.text.isEmpty - ? "Set TaskRc" - : "Taskrc file is verified", - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - Container( - height: 30, - width: 30, - decoration: BoxDecoration( - color: AppSettings.isDarkMode - ? TaskWarriorColors - .kLightSecondaryBackgroundColor - : TaskWarriorColors.ksecondaryBackgroundColor, - shape: BoxShape.circle, - ), - child: Center( - child: taskrcContentController.text.isNotEmpty - ? Icon( - Icons.check, - color: TaskWarriorColors.green, - ) - : Icon( - Icons.chevron_right_rounded, - color: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white, - ), - ), - ), - ], - ), - ), - ), - ], - ), - ), - Offstage( - offstage: isTaskDServerActive, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "TaskD Server Info", - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - const SizedBox(height: 10), - GestureDetector( - onTap: () {}, - child: Container( - width: MediaQuery.of(context).size.width * 1, - // height: MediaQuery.of(context).size.height * 0.05, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: tileColor, - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: TaskWarriorColors.borderColor, - ), - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - server == null - ? Text( - 'Not Configured', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ) - : Text( - '$server', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - Container( - height: 30, - width: 30, - decoration: BoxDecoration( - color: AppSettings.isDarkMode - ? TaskWarriorColors - .kLightSecondaryBackgroundColor - : TaskWarriorColors - .ksecondaryBackgroundColor, - shape: BoxShape.circle, - ), - child: Center( - child: server != null - ? Icon( - Icons.check, - color: TaskWarriorColors.green, - ) - : Icon( - Icons.chevron_right_rounded, - color: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white, - ), - ), - ), - ], - ), - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "TaskD Server Credentials", - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - const SizedBox(height: 10), - GestureDetector( - onTap: () {}, - child: Container( - width: MediaQuery.of(context).size.width * 1, - // height: MediaQuery.of(context).size.height * 0.05, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: tileColor, - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: TaskWarriorColors.borderColor), - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - credentialsString == null - ? Text( - 'Not Configured', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ) - : SizedBox( - width: MediaQuery.of(context) - .size - .width * - 0.7, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Text( - credentialsString, - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ), - ), - GestureDetector( - onTap: () { - hideKey = !hideKey; - setState(() {}); - }, - child: Container( - height: 30, - width: 30, - decoration: BoxDecoration( - color: AppSettings.isDarkMode - ? TaskWarriorColors - .kLightPrimaryBackgroundColor - : TaskWarriorColors - .kprimaryBackgroundColor, - shape: BoxShape.circle, - ), - child: credentials == null - ? Icon( - Icons.chevron_right_rounded, - color: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white, - ) - : Icon( - hideKey - ? Icons.visibility_off - : Icons.visibility, - color: AppSettings.isDarkMode - ? TaskWarriorColors.green - : TaskWarriorColors.green, - ), - ), - ), - ], - ), - ), - ), - ], - ), - ), - ], - )), - for (var pem in [ - 'taskd.certificate', - 'taskd.key', - 'taskd.ca', - if (StorageWidget.of(context).serverCertExists) 'server.cert', - ]) - PemWidget( - storage: storage, - pem: pem, - optionString: pem == "taskd.certificate" - ? "Configure your certificate" - : pem == "taskd.key" - ? "Configure TaskServer key" - : pem == "taskd.ca" - ? "Configure Server Certificate" - : "Configure Server Certificate", - listTileTitle: pem == "taskd.certificate" - ? "Select Certificate" - : pem == "taskd.key" - ? "Select key" - : pem == "taskd.ca" - ? "Select Certificate" - : "Select Certificate", - ), - ], - ), - ), - ); - } -} - -class PemWidget extends StatefulWidget { - const PemWidget( - {required this.storage, - required this.pem, - super.key, - required this.optionString, - required this.listTileTitle}); - - final Storage storage; - final String pem; - final String optionString; - final String listTileTitle; - @override - State createState() => _PemWidgetState(); -} - -class _PemWidgetState extends State { - @override - Widget build(BuildContext context) { - var contents = widget.storage.guiPemFiles.pemContents(widget.pem); - var name = widget.storage.guiPemFiles.pemFilename(widget.pem); - String identifier = ""; - try { - if (contents != null) { - identifier = fingerprint(contents).toUpperCase(); // Null check removed - } - } catch (e) { - debugPrint(e.toString()); - } - - var tileColor = AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor; - - return Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.optionString, - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - const SizedBox( - height: 10, - ), - GestureDetector( - onTap: (widget.pem == 'server.cert') - ? () { - widget.storage.guiPemFiles.removeServerCert(); - ProfilesWidget.of(context).setState( - () {}, - ); - setState( - () {}, - ); - } - : () async { - await setConfig( - storage: widget.storage, - key: widget.pem, - ); - setState( - () {}, - ); - }, - onLongPress: (widget.pem != 'server.cert' && name != null) - ? () { - widget.storage.guiPemFiles.removePemFile(widget.pem); - setState(() {}); - } - : null, - child: Container( - width: MediaQuery.of(context).size.width * 1, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: tileColor, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: TaskWarriorColors.borderColor), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox( - width: MediaQuery.of(context).size.width * 0.7, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - Text( - name == null - ? widget.listTileTitle - : (widget.pem == 'server.cert') - ? '' - : "$name = ", - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - Text( - widget.pem == 'taskd.key' - ? name != null - ? "private.key.pem" - : "" - : identifier, - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ], - ), - ), - ), - Container( - height: 30, - width: 30, - decoration: BoxDecoration( - color: AppSettings.isDarkMode - ? TaskWarriorColors.kLightSecondaryBackgroundColor - : TaskWarriorColors.ksecondaryBackgroundColor, - shape: BoxShape.circle, - ), - child: Center( - child: name == null - ? Icon( - Icons.chevron_right_rounded, - color: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white, - ) - : Icon( - Icons.check, - color: TaskWarriorColors.green, - ), - ), - ), - ], - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/views/Onboarding/onboarding_screen.dart b/lib/views/Onboarding/onboarding_screen.dart deleted file mode 100644 index 9f539e84..00000000 --- a/lib/views/Onboarding/onboarding_screen.dart +++ /dev/null @@ -1,232 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/controller/onboarding_controller.dart'; -import 'package:taskwarrior/views/Onboarding/Model/onboarding_contents.dart'; -import 'package:taskwarrior/views/Onboarding/Components/size_config.dart'; -import 'package:taskwarrior/views/home/home.dart'; - -class OnboardingScreen extends StatefulWidget { - const OnboardingScreen({super.key}); - - @override - State createState() => _OnboardingScreenState(); -} - -class _OnboardingScreenState extends State { - late PageController _controller; - - @override - void initState() { - _controller = PageController(); - super.initState(); - } - - int _currentPage = 0; - - AnimatedContainer _buildDots({int? index}) { - return AnimatedContainer( - duration: const Duration(milliseconds: 200), - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(50)), - color: TaskWarriorColors.black, - ), - margin: const EdgeInsets.only(right: 5), - height: 10, - curve: Curves.easeIn, - width: _currentPage == index ? 20 : 10, - ); - } - - @override - Widget build(BuildContext context) { - SizeConfig().init(context); - double width = SizeConfig.screenW!; - double height = SizeConfig.screenH!; - - return Scaffold( - backgroundColor: contents[_currentPage].colors, - body: SafeArea( - child: Column( - children: [ - Expanded( - flex: 3, - child: PageView.builder( - physics: const BouncingScrollPhysics(), - controller: _controller, - onPageChanged: (value) => setState(() => _currentPage = value), - itemCount: contents.length, - itemBuilder: (context, i) { - return _buildOnboardingPage(contents[i], width, height); - }, - ), - ), - SizedBox( - height: height * 5 / 100, - ), - Expanded( - flex: 1, - child: SingleChildScrollView(child: _buildBottomSection(width)), - ), - ], - ), - ), - ); - } - - Widget _buildOnboardingPage( - OnboardingModel content, double width, double height) { - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(35.0), - child: Column( - children: [ - SvgPicture.asset( - content.image, - height: SizeConfig.blockV! * 30, - ), - SizedBox( - height: (height >= 840) ? 60 : 30, - ), - Text( - content.title, - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.semiBold, - fontSize: (width <= 550) ? 30 : 35, - ), - ), - SizedBox( - height: height * 2 / 100, - ), - // Flexible(flex: 1, child: Container()), - Text( - content.desc, - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.light, - fontSize: (width <= 550) ? 17 : 17, - ), - textAlign: TextAlign.center, - ), - SizedBox( - height: height * 2 / 100, - ) - // Flexible(flex: 1, child: Container()), - ], - ), - ), - ); - } - - Widget _buildBottomSection(double width) { - return Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate( - contents.length, - (int index) => _buildDots(index: index), - ), - ), - _currentPage + 1 == contents.length - ? _buildStartButton(width) - : _buildNextButton(width), - ], - ); - } - - Widget _buildStartButton(double width) { - final OnboardingController onboardingController = OnboardingController(); - - return Padding( - padding: const EdgeInsets.all(30), - child: ElevatedButton( - onPressed: () { - onboardingController.markOnboardingAsCompleted(); - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute(builder: (context) => const HomePage()), - (Route route) => false); - }, - style: ElevatedButton.styleFrom( - backgroundColor: TaskWarriorColors.black, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(50), - ), - padding: (width <= 550) - ? const EdgeInsets.symmetric(horizontal: 100, vertical: 20) - : EdgeInsets.symmetric(horizontal: width * 0.2, vertical: 25), - textStyle: GoogleFonts.poppins(fontSize: (width <= 550) ? 13 : 17), - ), - child: Text( - "Start", - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.light, - color: TaskWarriorColors.white, - fontSize: (width <= 550) ? 17 : 17, - ), - ), - ), - ); - } - - Widget _buildNextButton(double width) { - return Padding( - padding: const EdgeInsets.all(30), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: () { - _controller.jumpToPage(2); - }, - style: TextButton.styleFrom( - elevation: 0, - textStyle: TextStyle( - fontWeight: TaskWarriorFonts.semiBold, - fontSize: (width <= 550) ? 13 : 17, - ), - ), - child: Text( - "Skip", - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - color: TaskWarriorColors.black, - fontSize: (width <= 550) ? 12 : 12, - ), - ), - ), - ElevatedButton( - onPressed: () { - _controller.nextPage( - duration: const Duration(milliseconds: 200), - curve: Curves.easeIn, - ); - }, - style: ElevatedButton.styleFrom( - backgroundColor: TaskWarriorColors.black, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(50), - ), - elevation: 0, - padding: (width <= 550) - ? const EdgeInsets.symmetric(horizontal: 30, vertical: 20) - : const EdgeInsets.symmetric(horizontal: 30, vertical: 25), - textStyle: TextStyle(fontSize: (width <= 550) ? 13 : 17), - ), - child: Text( - "Next", - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.light, - color: TaskWarriorColors.white, - fontSize: (width <= 550) ? 12 : 12, - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/views/about/about.dart b/lib/views/about/about.dart deleted file mode 100644 index b8cea767..00000000 --- a/lib/views/about/about.dart +++ /dev/null @@ -1,284 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:sizer/sizer.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:url_launcher/url_launcher.dart'; - -class AboutPage extends StatefulWidget { - const AboutPage({super.key}); - @override - // ignore: library_private_types_in_public_api - _AboutPageState createState() => _AboutPageState(); -} - -class _AboutPageState extends State { - String introduction = - "This project aims to build an app for Taskwarrior. It is your task management app across all platforms. It helps you manage your tasks and filter them as per your needs."; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - centerTitle: true, - backgroundColor: TaskWarriorColors.kprimaryBackgroundColor, - title: Text( - 'About', - style: GoogleFonts.poppins(color: TaskWarriorColors.white), - ), - leading: GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: Icon( - Icons.chevron_left, - color: TaskWarriorColors.white, - ), - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.white, - body: Padding( - padding: EdgeInsets.only(top: 1.h, left: 2.w, right: 2.w), - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - child: SvgPicture.asset( - "assets/svg/logo.svg", - height: 20.h, - width: 100.w, - )), - SizedBox( - height: 2.h, - ), - Text( - "Taskwarrior", - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeLarge, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - SizedBox( - height: 2.h, - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FutureBuilder( - future: getAppInfo(), - builder: - (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const CircularProgressIndicator(); - } else if (snapshot.hasError) { - return Text('Error: ${snapshot.error}'); - } else { - final appInfoLines = snapshot.data!.split(' '); - - return Column( - children: [ - RichText( - text: TextSpan( - children: [ - TextSpan( - text: 'Version: ', - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - TextSpan( - text: appInfoLines[1], - style: GoogleFonts.poppins( - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ], - ), - ), - SizedBox( - width: 85.w, - child: FittedBox( - fit: BoxFit.fitWidth, - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: 'Package: ', - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: - TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - TextSpan( - text: appInfoLines[0], - style: GoogleFonts.poppins( - fontSize: - TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ], - ), - ), - ), - ), - ], - ); - } - }, - ), - ], - ), - SizedBox( - height: 5.h, - ), - Text( - introduction, - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.medium, - fontSize: TaskWarriorFonts.fontSizeSmall, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - SizedBox( - height: 6.h, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - SizedBox( - width: 40.w, - height: 5.h, - child: ElevatedButton.icon( - style: ElevatedButton.styleFrom( - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kLightSecondaryBackgroundColor - : TaskWarriorColors.ksecondaryBackgroundColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - onPressed: () async { - // Launch GitHub URL. - - String url = - "https://github.com/CCExtractor/taskwarrior-flutter"; - if (!await launchUrl(Uri.parse(url))) { - throw Exception('Could not launch $url'); - } - }, - icon: SvgPicture.asset("assets/svg/github.svg", - width: 15.sp, - height: 15.sp, - colorFilter: ColorFilter.mode( - AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white, - BlendMode.srcIn)), - label: Text( - "GitHub", - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.medium, - fontSize: TaskWarriorFonts.fontSizeSmall, - color: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white, - ), - ), - ), - ), - SizedBox( - width: 40.w, - height: 5.h, - child: ElevatedButton.icon( - style: ElevatedButton.styleFrom( - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kLightSecondaryBackgroundColor - : TaskWarriorColors.ksecondaryBackgroundColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - onPressed: () async { - String url = "https://ccextractor.org/"; - if (!await launchUrl(Uri.parse(url))) { - throw Exception('Could not launch $url'); - } - }, - icon: SvgPicture.asset("assets/svg/link.svg", - width: 15.sp, - height: 15.sp, - colorFilter: ColorFilter.mode( - AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white, - BlendMode.srcIn)), - label: Text( - "CCExtractor", - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.medium, - fontSize: TaskWarriorFonts.fontSizeSmall, - color: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white, - ), - ), - ), - ), - ], - ), - SizedBox( - height: 2.h, - ), - Text( - "Eager to enhance this project? Visit our GitHub repository.", - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.semiBold, - fontSize: TaskWarriorFonts.fontSizeSmall, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - SizedBox( - height: 2.h, - ), - ], - ), - ), - ), - ); - } -} - -Future getAppInfo() async { - PackageInfo packageInfo = await PackageInfo.fromPlatform(); - - return '${packageInfo.packageName} ${packageInfo.version}'; -} diff --git a/lib/views/home/home.dart b/lib/views/home/home.dart deleted file mode 100644 index bf478b5d..00000000 --- a/lib/views/home/home.dart +++ /dev/null @@ -1,438 +0,0 @@ -// ignore_for_file: use_build_context_synchronously, library_private_types_in_public_api - -import 'package:flutter/material.dart'; - -import 'package:double_back_to_close_app/double_back_to_close_app.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:home_widget/home_widget.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/controller/home_tour_controller.dart'; -import 'package:taskwarrior/drawer/filter_drawer.dart'; -import 'package:taskwarrior/drawer/nav_drawer.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/services/task_details.dart'; -import 'package:taskwarrior/taskserver/ntaskserver.dart'; -import 'package:taskwarrior/views/home/home_tour.dart'; -import 'package:taskwarrior/widgets/add_Task.dart'; -import 'package:taskwarrior/widgets/buildTasks.dart'; -import 'package:taskwarrior/widgets/pallete.dart'; -import 'package:taskwarrior/widgets/tag_filter.dart'; - -import 'package:taskwarrior/model/storage.dart'; - -import 'package:taskwarrior/widgets/home_paths.dart' as rc; -import 'package:taskwarrior/widgets/taskserver.dart'; -import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; - -class Filters { - const Filters({ - required this.pendingFilter, - required this.waitingFilter, - required this.togglePendingFilter, - required this.toggleWaitingFilter, - required this.tagFilters, - required this.projects, - required this.projectFilter, - required this.toggleProjectFilter, - }); - - final bool pendingFilter; - final bool waitingFilter; - final void Function() togglePendingFilter; - final void Function() toggleWaitingFilter; - final TagFilters tagFilters; - final dynamic projects; - final String projectFilter; - final void Function(String) toggleProjectFilter; -} - -class HomePage extends StatefulWidget { - static const String routeName = '/home'; - - const HomePage({super.key}); - @override - _HomePageState createState() => _HomePageState(); -} - -class _HomePageState extends State { - final addKey = GlobalKey(); - final searchKey1 = GlobalKey(); - final searchKey2 = GlobalKey(); - final filterKey = GlobalKey(); - final menuKey = GlobalKey(); - final refreshKey = GlobalKey(); - - bool isSaved = false; - late TutorialCoachMark tutorialCoachMark; - - void _initInAppTour() { - tutorialCoachMark = TutorialCoachMark( - targets: addTargetsPage( - addKey: addKey, - searchKey: searchKey1, - filterKey: filterKey, - menuKey: menuKey, - refreshKey: refreshKey, - ), - colorShadow: TaskWarriorColors.black, - paddingFocus: 10, - opacityShadow: 0.8, - hideSkip: true, - onFinish: () { - SaveInAppTour().saveTourStatus(); - }); - } - - void _showInAppTour() { - Future.delayed( - const Duration(seconds: 2), - () { - SaveInAppTour().getTourStatus().then((value) => { - if (value == false) - { - tutorialCoachMark.show(context: context), - } - else - { - // ignore: avoid_print - debugPrint('User has seen this page'), - // User has seen this page - } - }); - }, - ); - } - - @override - void initState() { - super.initState(); - _initInAppTour(); - _showInAppTour(); - } - - late InheritedStorage storageWidget; - late Storage storage; - Server? server; - Credentials? credentials; - - bool isTaskDServerActive = true; - - ///to check if the data is synced or not - - bool isSyncNeeded = false; - - ///call the synchronize function from storage_widget.dart - ///to sync the data from the server - @override - void didChangeDependencies() { - super.didChangeDependencies(); - storage = StorageWidget.of(context).storage; - - ///didChangeDependencies loads after the initState - ///it provides the context from the tree - if (!isSyncNeeded) { - ///check if the data is synced or not - ///if not then sync the data - isNeededtoSyncOnStart(); - isSyncNeeded = true; - } - } - - isNeededtoSyncOnStart() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - bool? value; - value = prefs.getBool('sync-onStart') ?? false; - - if (value) { - storageWidget = StorageWidget.of(context); - storageWidget.synchronize(context, false); - } else {} - } - - bool hideKey = true; - bool isHomeWidgetTaskTapped = false; - late String uuid; - @override - Widget build(BuildContext context) { - - HomeWidget.widgetClicked.listen((uri) async{ - // print('i am here and uri is $uri'); - // print('is tapped is i am being called'); - if (uri != null) { - if (uri.host == "cardclicked") { - if (uri.queryParameters["uuid"] != null) { - uuid = uri.queryParameters["uuid"] as String; - setState(() { - isHomeWidgetTaskTapped = true; - }); - // print('is tapped is $isHomeWidgetTaskTapped'); - } - debugPrint('uuid is $uuid'); - } - } - - }); - Server? server; - Credentials? credentials; - - var contents = rc.Taskrc(storage.home.home).readTaskrc(); - if (contents != null) { - server = Taskrc.fromString(contents).server; - credentials = Taskrc.fromString(contents).credentials; - } - - if (contents != null) { - server = Taskrc.fromString(contents).server; - credentials = Taskrc.fromString(contents).credentials; - } - - var storageWidget = StorageWidget.of(context); - var taskData = storageWidget.tasks; - - var pendingFilter = storageWidget.pendingFilter; - var waitingFilter = storageWidget.waitingFilter; - var pendingTags = storageWidget.pendingTags; - - var selectedTagsMap = { - for (var tag in storageWidget.selectedTags) tag.substring(1): tag, - }; - - var keys = (pendingTags.keys.toSet()..addAll(selectedTagsMap.keys)).toList() - ..sort(); - - var tags = { - for (var tag in keys) - tag: TagFilterMetadata( - display: - '${selectedTagsMap[tag] ?? tag} ${pendingTags[tag]?.frequency ?? 0}', - selected: selectedTagsMap.containsKey(tag), - ), - }; - - var tagFilters = TagFilters( - tagUnion: storageWidget.tagUnion, - toggleTagUnion: storageWidget.toggleTagUnion, - tags: tags, - toggleTagFilter: storageWidget.toggleTagFilter, - ); - var filters = Filters( - pendingFilter: pendingFilter, - waitingFilter: waitingFilter, - togglePendingFilter: storageWidget.togglePendingFilter, - toggleWaitingFilter: storageWidget.toggleWaitingFilter, - projects: storageWidget.projects, - projectFilter: storageWidget.projectFilter, - toggleProjectFilter: storageWidget.toggleProjectFilter, - tagFilters: tagFilters, - ); - - return isHomeWidgetTaskTapped == false ? Scaffold( - appBar: AppBar( - backgroundColor: TaskWarriorColors.kprimaryBackgroundColor, - surfaceTintColor: TaskWarriorColors.kprimaryBackgroundColor, - title: Text('Home Page', - style: GoogleFonts.poppins(color: TaskWarriorColors.white)), - actions: [ - IconButton( - key: searchKey1, - icon: (storageWidget.searchVisible) - ? Tooltip( - message: 'Cancel', - child: Icon(Icons.cancel, color: TaskWarriorColors.white)) - : Tooltip( - message: 'Search', - child: Icon(Icons.search, color: TaskWarriorColors.white)), - onPressed: storageWidget.toggleSearch, - ), - Builder( - builder: (context) => IconButton( - key: refreshKey, - icon: Icon(Icons.refresh, color: TaskWarriorColors.white), - onPressed: () { - if (server != null || credentials != null) { - storageWidget.synchronize(context, true); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - content: Text( - 'TaskServer is not configured', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - action: SnackBarAction( - label: 'Set Up', - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const ManageTaskServer(), - )).then((value) { - setState(() {}); - }); - }, - textColor: TaskWarriorColors.purple, - ), - ), - ); - } - }, - ), - ), - Builder( - builder: (context) => IconButton( - key: filterKey, - icon: Tooltip( - message: 'Filters', - child: Icon(Icons.filter_list, color: TaskWarriorColors.white), - ), - onPressed: () => Scaffold.of(context).openEndDrawer(), - ), - ), - ], - leading: Builder( - builder: (context) => IconButton( - key: menuKey, - icon: Tooltip( - message: 'Menu', - child: Icon(Icons.menu, color: TaskWarriorColors.white)), - onPressed: () => Scaffold.of(context).openDrawer(), - ), - ), - ), - drawer: NavDrawer(storageWidget: storageWidget, notifyParent: refresh), - body: DoubleBackToCloseApp( - snackBar: const SnackBar(content: Text('Tap back again to exit')), - child: Container( - color: AppSettings.isDarkMode - ? Palette.kToDark.shade200 - : TaskWarriorColors.white, - child: Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0), - child: Column( - children: [ - if (storageWidget.searchVisible) - Container( - key: searchKey2, - margin: const EdgeInsets.symmetric( - horizontal: 10, vertical: 10), - child: SearchBar( - backgroundColor: MaterialStateProperty.all( - (TaskWarriorColors.kLightPrimaryBackgroundColor)), - surfaceTintColor: MaterialStateProperty.all( - (TaskWarriorColors.kLightPrimaryBackgroundColor)), - controller: storageWidget.searchController, - // shape:, - onChanged: (value) { - storageWidget.search(value); - }, - autoFocus: true, - - shape: MaterialStateProperty.resolveWith( - (Set states) { - if (states.contains(MaterialState.focused)) { - return RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0), - side: BorderSide( - color: TaskWarriorColors.black, - width: 2.0, - ), - ); - } else { - return RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0), - side: BorderSide( - color: TaskWarriorColors.black, - width: 1.5, - ), - ); - } - }, - ), - leading: const Icon(Icons.search_rounded), - trailing: [ - (storageWidget.searchController.text.isNotEmpty) - ? IconButton( - key: GlobalKey(), - icon: Icon(Icons.cancel, - color: TaskWarriorColors.black), - onPressed: () { - storageWidget.searchController.clear(); - storageWidget.search( - storageWidget.searchController.text); - }, - ) - : const SizedBox( - width: 0, - height: 0, - ) - ], - - hintText: 'Search', - ), - ), - Expanded( - child: Scrollbar( - child: TasksBuilder( - // darkmode: AppSettings.isDarkMode, - taskData: taskData, - pendingFilter: pendingFilter, - waitingFilter: waitingFilter, - searchVisible: storageWidget.searchVisible), - ), - ), - ], - ), - ), - ), - ), - endDrawer: FilterDrawer(filters), - floatingActionButton: FloatingActionButton( - key: addKey, - heroTag: "btn3", - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kLightPrimaryBackgroundColor - : TaskWarriorColors.kprimaryBackgroundColor, - child: Tooltip( - message: 'Add Task', - child: Icon( - Icons.add, - color: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.white, - ), - ), - onPressed: () => showDialog( - context: context, - builder: (context) => const AddTaskBottomSheet(), - ).then((value) { - // print(value); - - //if auto sync is turned on - if (isSyncNeeded) { - //if user have not created any event then - //we won't call sync method - if (value == "cancel") { - } else { - //else we can sync new tasks - isNeededtoSyncOnStart(); - } - } - }), - ), - resizeToAvoidBottomInset: false, - ) : DetailRoute(uuid); - } - - refresh() { - setState(() {}); - } -} diff --git a/lib/views/profile/profile.dart b/lib/views/profile/profile.dart deleted file mode 100644 index fed6d722..00000000 --- a/lib/views/profile/profile.dart +++ /dev/null @@ -1,289 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; - -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/model/storage/savefile.dart'; -import 'package:taskwarrior/taskserver/ntaskserver.dart'; -import 'package:taskwarrior/utility/utilities.dart'; -import 'package:taskwarrior/widgets/pallete.dart'; -import 'package:taskwarrior/widgets/profilefunctions.dart'; -import 'package:taskwarrior/widgets/taskdetails.dart'; - -class ProfilePage extends StatefulWidget { - static const String routeName = '/profile'; - - const ProfilePage({super.key}); - @override - // ignore: library_private_types_in_public_api - _ProfilePageState createState() => _ProfilePageState(); -} - -class _ProfilePageState extends State { - @override - Widget build(BuildContext context) { - var profilesWidget = ProfilesWidget.of(context); - - var profilesMap = ProfilesWidget.of(context).profilesMap; - var currentProfile = ProfilesWidget.of(context).currentProfile; - - return Scaffold( - appBar: AppBar( - backgroundColor: Palette.kToDark.shade200, - title: Text( - profilesMap.length == 1 ? 'Profile' : 'Profiles', - style: GoogleFonts.poppins(color: TaskWarriorColors.white), - ), - leading: IconButton( - onPressed: () { - // Navigator.pushReplacementNamed(context, PageRoutes.home); - Navigator.of(context).pop(); - }, - icon: Icon( - Icons.chevron_left, - color: TaskWarriorColors.white, - size: 30, - ), - ), - ), - //primary: false, - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.kLightPrimaryBackgroundColor, - body: SingleChildScrollView( - child: Column( - children: [ - ProfilesColumn( - profilesMap, - currentProfile, - profilesWidget.addProfile, - profilesWidget.selectProfile, - () => showDialog( - context: context, - builder: (context) => Center( - child: RenameProfileDialog( - profile: currentProfile, - alias: profilesMap[currentProfile], - context: context, - ), - ), - ), - () => Navigator.push( - context, - MaterialPageRoute( - // builder: (_) => const ConfigureTaskserverRoute(), - builder: (_) => const ManageTaskServer(), - ), - ), - () { - var tasks = - profilesWidget.getStorage(currentProfile).data.export(); - var now = DateTime.now() - .toIso8601String() - .replaceAll(RegExp(r'[-:]'), '') - .replaceAll(RegExp(r'\..*'), ''); - - showDialog( - context: context, - builder: (BuildContext context) { - return Utils.showAlertDialog( - title: Text( - "Export Format", - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - content: Text( - "Choose the export format:", - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - actions: [ - TextButton( - child: Text( - "JSON", - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - exportTasks( - contents: tasks, - suggestedName: 'tasks-$now.json', - ); - }, - ), - TextButton( - child: Text( - "TXT", - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - exportTasks( - contents: tasks, - suggestedName: 'tasks-$now.txt', - ); - }, - ), - ], - ); - }, - ); - }, - () { - try { - profilesWidget.copyConfigToNewProfile(currentProfile); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - 'Profile Config Copied', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } catch (e) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - 'Profile Config Copy Failed', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } - }, - () => showDialog( - context: context, - builder: (context) => DeleteProfileDialog( - profile: currentProfile, - context: context, - ), - ), - ), - ], - ), - ), - ); - } -} - -class ProfilesColumn extends StatelessWidget { - const ProfilesColumn( - this.profilesMap, - this.currentProfile, - this.addProfile, - this.selectProfile, - this.rename, - this.configure, - this.export, - this.copy, - this.delete, { - super.key, - }); - - final Map profilesMap; - final String currentProfile; - final void Function() addProfile; - final void Function(String) selectProfile; - final void Function() rename; - final void Function() configure; - final void Function() export; - final void Function() copy; - final void Function() delete; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(top: 10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SelectProfile(currentProfile, profilesMap, selectProfile), - const SizedBox( - height: 6, - ), - ManageProfile(rename, configure, export, copy, delete), - const SizedBox( - height: 6, - ), - ElevatedButton.icon( - onPressed: () { - try { - addProfile(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - 'Profile Added Successfully', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightPrimaryTextColor, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } catch (e) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - 'Profile Additon Failed', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightPrimaryTextColor, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } - }, - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - ), - ), - icon: Icon(Icons.add, - color: AppSettings.isDarkMode - ? TaskWarriorColors.deepPurpleAccent - : TaskWarriorColors.deepPurple), - label: Text( - 'Add new Profile', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ) - ], - ), - ); - } -} diff --git a/lib/views/reports/pages/burndown_daily.dart b/lib/views/reports/pages/burndown_daily.dart deleted file mode 100644 index 61f85045..00000000 --- a/lib/views/reports/pages/burndown_daily.dart +++ /dev/null @@ -1,223 +0,0 @@ -// ignore_for_file: prefer_typing_uninitialized_variables - -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'dart:io'; - -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/model/chart.dart'; -import 'package:taskwarrior/model/json/task.dart'; -import 'package:taskwarrior/model/storage.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/utility/utilities.dart'; -import 'package:taskwarrior/views/home/home.dart'; -import 'package:taskwarrior/views/reports/widgets/commonChartIndicator.dart'; -import 'package:taskwarrior/widgets/taskdetails/profiles_widget.dart'; - -class BurnDownDaily extends StatefulWidget { - const BurnDownDaily({super.key}); - - @override - State createState() => _BurnDownDailyState(); -} - -class _BurnDownDailyState extends State - with TickerProviderStateMixin { - var storageWidget; - late Storage storage; - late final Filters filters; - List taskData = []; - List dailyBurnDown = []; - Directory? baseDirectory; - List allData = []; - List completedTasks = []; - List pendingTasks = []; - List startedTasks = []; - - @override - void initState() { - super.initState(); - - ///initialize the _dailyBurndownTooltipBehaviour tooltip behavior - _dailyBurndownTooltipBehaviour = TooltipBehavior( - enable: true, - builder: (dynamic data, dynamic point, dynamic series, int pointIndex, - int seriesIndex) { - final String date = data.x; - final int pendingCount = data.y1; - final int completedCount = data.y2; - - return Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(5), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Date: $date', - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - ), - ), - Text( - 'Pending: $pendingCount', - ), - Text( - 'Completed: $completedCount', - ), - ], - ), - ); - }, - ); - - ///initialize the storage widget - Future.delayed(Duration.zero, () { - storageWidget = StorageWidget.of(context); - var currentProfile = ProfilesWidget.of(context).currentProfile; - - Directory baseDirectory = ProfilesWidget.of(context).getBaseDirectory(); - setState(() { - storage = Storage( - Directory('${baseDirectory.path}/profiles/$currentProfile'), - ); - }); - - ///fetch all data contains all the tasks - allData = storage.data.allData(); - - ///check if allData is not empty - if (allData.isNotEmpty) { - ///sort the data by daily burn down - sortBurnDownDaily(); - } - }); - } - - /// dailyInfo is a map that contains the daily burn down data - /// The key is the date (formatted as "MM-dd") and the value is a map - /// containing the pending and completed tasks count for that day. - Map> dailyInfo = {}; - - void sortBurnDownDaily() { - // Initialize dailyInfo map - dailyInfo = {}; - - // Sort allData by entry date in ascending order - allData.sort((a, b) => a.entry.compareTo(b.entry)); - - /// Loop through allData and get the date - for (int i = 0; i < allData.length; i++) { - final String date = Utils.formatDate(allData[i].entry, 'MM-dd'); - - /// Check if dailyInfo contains the date - if (dailyInfo.containsKey(date)) { - /// Check if the status is pending or completed - if (allData[i].status == 'pending') { - /// If the status is pending, then add 1 to the pending count - dailyInfo[date]!['pending'] = (dailyInfo[date]!['pending'] ?? 0) + 1; - } else if (allData[i].status == 'completed') { - /// If the status is completed, then add 1 to the completed count - dailyInfo[date]!['completed'] = - (dailyInfo[date]!['completed'] ?? 0) + 1; - } - } else { - /// If dailyInfo does not contain the date - dailyInfo[date] = { - 'pending': allData[i].status == 'pending' ? 1 : 0, - 'completed': allData[i].status == 'completed' ? 1 : 0, - }; - } - } - - debugPrint("dailyInfo $dailyInfo"); - } - - /// This method is used to get the daily burn down data - late TooltipBehavior _dailyBurndownTooltipBehaviour; - @override - Widget build(BuildContext context) { - double height = MediaQuery.of(context).size.height; // Screen height - // double width = MediaQuery.of(context).size.width; // Screen width - - return Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: SizedBox( - height: height * 0.6, - child: SfCartesianChart( - primaryXAxis: CategoryAxis( - title: AxisTitle( - text: 'Day - Month', - textStyle: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - color: AppSettings.isDarkMode ? Colors.white : Colors.black, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - ), - ), - primaryYAxis: NumericAxis( - title: AxisTitle( - text: 'Tasks', - textStyle: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeSmall, - color: AppSettings.isDarkMode ? Colors.white : Colors.black, - ), - ), - ), - tooltipBehavior: _dailyBurndownTooltipBehaviour, - series: [ - /// This is the completed tasks - StackedColumnSeries( - groupName: 'Group A', - enableTooltip: true, - color: TaskWarriorColors.green, - dataSource: dailyInfo.entries - .map((entry) => ChartData( - entry.key, - entry.value['pending'] ?? 0, - entry.value['completed'] ?? 0, - )) - .toList(), - xValueMapper: (ChartData data, _) => data.x, - yValueMapper: (ChartData data, _) => data.y2, - name: 'Completed', - ), - - /// This is the pending tasks - StackedColumnSeries( - groupName: 'Group A', - color: TaskWarriorColors.yellow, - enableTooltip: true, - dataSource: dailyInfo.entries - .map((entry) => ChartData( - entry.key, - entry.value['pending'] ?? 0, - entry.value['completed'] ?? 0, - )) - .toList(), - xValueMapper: (ChartData data, _) => data.x, - yValueMapper: (ChartData data, _) => data.y1, - name: 'Pending', - ), - ], - ), - ), - ), - const CommonChartIndicator( - title: 'Daily Burndown Chart', - ), - ], - ); - } -} diff --git a/lib/views/reports/pages/burndown_monthly.dart b/lib/views/reports/pages/burndown_monthly.dart deleted file mode 100644 index 4d5eed7b..00000000 --- a/lib/views/reports/pages/burndown_monthly.dart +++ /dev/null @@ -1,204 +0,0 @@ -// ignore_for_file: prefer_typing_uninitialized_variables - -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/model/chart.dart'; -import 'dart:io'; - -import 'package:taskwarrior/model/json/task.dart'; -import 'package:taskwarrior/model/storage.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/utility/utilities.dart'; -import 'package:taskwarrior/views/reports/widgets/commonChartIndicator.dart'; -import 'package:taskwarrior/widgets/taskdetails/profiles_widget.dart'; - -class BurnDownMonthlt extends StatefulWidget { - const BurnDownMonthlt({super.key}); - - @override - State createState() => _BurnDownMonthltState(); -} - -class _BurnDownMonthltState extends State - with TickerProviderStateMixin { - late Storage storage; - List taskData = []; - Map> monthlyInfo = {}; - Directory? baseDirectory; - List allData = []; - List completedTasks = []; - List pendingTasks = []; - List startedTasks = []; - - late TooltipBehavior _weeklyBurndownTooltipBehaviour; - var storageWidget; - - @override - void initState() { - super.initState(); - - _weeklyBurndownTooltipBehaviour = TooltipBehavior( - enable: true, - builder: (dynamic data, dynamic point, dynamic series, int pointIndex, - int seriesIndex) { - final String monthYear = data.x; - final int pendingCount = data.y1; - final int completedCount = data.y2; - - return Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(5), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Month-Year: $monthYear', - style: const TextStyle( - fontWeight: TaskWarriorFonts.bold, - ), - ), - Text( - 'Pending: $pendingCount', - ), - Text( - 'Completed: $completedCount', - ), - ], - ), - ); - }, - ); - - ///initialize the storage widget - Future.delayed(Duration.zero, () { - storageWidget = StorageWidget.of(context); - var currentProfile = ProfilesWidget.of(context).currentProfile; - - Directory baseDirectory = ProfilesWidget.of(context).getBaseDirectory(); - setState(() { - storage = Storage( - Directory('${baseDirectory.path}/profiles/$currentProfile'), - ); - }); - - ///fetch all data contains all the tasks - allData = storage.data.allData(); - - ///check if allData is not empty - if (allData.isNotEmpty) { - ///sort the data by weekly burn down - sortBurnDownMonthly(); - } - }); - } - - void sortBurnDownMonthly() { - monthlyInfo = {}; - - allData.sort((a, b) => a.entry.compareTo(b.entry)); - - for (int i = 0; i < allData.length; i++) { - final DateTime entryDate = allData[i].entry; - final String monthYear = - '${Utils.getMonthName(entryDate.month)} ${entryDate.year}'; - - if (monthlyInfo.containsKey(monthYear)) { - if (allData[i].status == 'pending') { - monthlyInfo[monthYear]!['pending'] = - (monthlyInfo[monthYear]!['pending'] ?? 0) + 1; - } else if (allData[i].status == 'completed') { - monthlyInfo[monthYear]!['completed'] = - (monthlyInfo[monthYear]!['completed'] ?? 0) + 1; - } - } else { - monthlyInfo[monthYear] = { - 'pending': allData[i].status == 'pending' ? 1 : 0, - 'completed': allData[i].status == 'completed' ? 1 : 0, - }; - } - } - - debugPrint("monthlyInfo: $monthlyInfo"); - } - - @override - Widget build(BuildContext context) { - final double height = MediaQuery.of(context).size.height; - return Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: SizedBox( - height: height * 0.6, - child: SfCartesianChart( - primaryXAxis: CategoryAxis( - title: AxisTitle( - text: 'Month - Year', - textStyle: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeSmall, - color: AppSettings.isDarkMode ? Colors.white : Colors.black, - ), - ), - ), - primaryYAxis: NumericAxis( - title: AxisTitle( - text: 'Tasks', - textStyle: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeSmall, - color: AppSettings.isDarkMode ? Colors.white : Colors.black, - ), - ), - ), - tooltipBehavior: _weeklyBurndownTooltipBehaviour, - series: [ - StackedColumnSeries( - groupName: 'Group A', - enableTooltip: true, - color: Colors.green, - dataSource: monthlyInfo.entries - .map((entry) => ChartData( - entry.key, - entry.value['pending'] ?? 0, - entry.value['completed'] ?? 0, - )) - .toList(), - xValueMapper: (ChartData data, _) => data.x, - yValueMapper: (ChartData data, _) => data.y2, - name: 'Completed', - ), - StackedColumnSeries( - groupName: 'Group A', - color: Colors.yellow, - enableTooltip: true, - dataSource: monthlyInfo.entries - .map((entry) => ChartData( - entry.key, - entry.value['pending'] ?? 0, - entry.value['completed'] ?? 0, - )) - .toList(), - xValueMapper: (ChartData data, _) => data.x, - yValueMapper: (ChartData data, _) => data.y1, - name: 'Pending', - ), - ], - ), - ), - ), - const CommonChartIndicator( - title: 'Monthly Burndown Chart', - ) - ], - ); - } -} diff --git a/lib/views/reports/pages/burndown_weekly.dart b/lib/views/reports/pages/burndown_weekly.dart deleted file mode 100644 index f88d5767..00000000 --- a/lib/views/reports/pages/burndown_weekly.dart +++ /dev/null @@ -1,231 +0,0 @@ -// ignore_for_file: prefer_typing_uninitialized_variables - -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'dart:io'; - -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/model/chart.dart'; -import 'package:taskwarrior/model/json/task.dart'; -import 'package:taskwarrior/model/storage.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/utility/utilities.dart'; -import 'package:taskwarrior/views/home/home.dart'; -import 'package:taskwarrior/views/reports/widgets/commonChartIndicator.dart'; -import 'package:taskwarrior/widgets/taskdetails/profiles_widget.dart'; - -class BurnDownWeekly extends StatefulWidget { - const BurnDownWeekly({super.key}); - - @override - State createState() => _BurnDownWeeklyState(); -} - -class _BurnDownWeeklyState extends State - with TickerProviderStateMixin { - var storageWidget; - late Storage storage; - late final Filters filters; - List taskData = []; - List weeklyBurnDown = []; - Directory? baseDirectory; - List allData = []; - List completedTasks = []; - List pendingTasks = []; - List startedTasks = []; - @override - void initState() { - super.initState(); - - ///initialize the _weeklyBurndownTooltipBehaviour tooltip behavior - _weeklyBurndownTooltipBehaviour = TooltipBehavior( - enable: true, - builder: (dynamic data, dynamic point, dynamic series, int pointIndex, - int seriesIndex) { - final String weekNumber = data.x; - final int pendingCount = data.y1; - final int completedCount = data.y2; - - return Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(5), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - weekNumber, - style: const TextStyle( - fontWeight: TaskWarriorFonts.bold, - ), - ), - Text( - 'Pending: $pendingCount', - ), - Text( - 'Completed: $completedCount', - ), - ], - ), - ); - }, - ); - - ///initialize the storage widget - Future.delayed(Duration.zero, () { - storageWidget = StorageWidget.of(context); - var currentProfile = ProfilesWidget.of(context).currentProfile; - - Directory baseDirectory = ProfilesWidget.of(context).getBaseDirectory(); - setState(() { - storage = Storage( - Directory('${baseDirectory.path}/profiles/$currentProfile'), - ); - }); - - ///fetch all data contains all the tasks - allData = storage.data.allData(); - - ///check if allData is not empty - if (allData.isNotEmpty) { - ///sort the data by weekly burn down - sortBurnDownWeekLy(); - } - }); - } - - ///weeklyInfo is a map that contains the weekly burn down data - ///first int holds the week value - ///the second map holds the pending and completed tasks - ///the key is the status and the value is the count - Map> weeklyInfo = {}; - - void sortBurnDownWeekLy() { - // Initialize weeklyInfo map - weeklyInfo = {}; - - // Sort allData by entry date in ascending order - allData.sort((a, b) => a.entry.compareTo(b.entry)); - - ///loop through allData and get the week number - for (int i = 0; i < allData.length; i++) { - final int weekNumber = Utils.getWeekNumbertoInt(allData[i].entry); - - ///check if weeklyInfo contains the week number - if (weeklyInfo.containsKey(weekNumber)) { - ///check if the status is pending or completed - if (allData[i].status == 'pending') { - ///if the status is pending then add 1 to the pending count - weeklyInfo[weekNumber]!['pending'] = - (weeklyInfo[weekNumber]!['pending'] ?? 0) + 1; - } else if (allData[i].status == 'completed') { - ///if the status is completed then add 1 to the completed count - weeklyInfo[weekNumber]!['completed'] = - (weeklyInfo[weekNumber]!['completed'] ?? 0) + 1; - } - } else { - ///if weeklyInfo does not contain the week number - weeklyInfo[weekNumber] = { - 'pending': allData[i].status == 'pending' ? 1 : 0, - 'completed': allData[i].status == 'completed' ? 1 : 0, - }; - } - } - - debugPrint("weeklyInfo $weeklyInfo"); - } - - ///this method is used to get the weekly burn down data - late TooltipBehavior _weeklyBurndownTooltipBehaviour; - @override - Widget build(BuildContext context) { - double height = MediaQuery.of(context).size.height; // Screen height - // double width = MediaQuery.of(context).size.width; // Screen width - return Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: SizedBox( - height: height * 0.6, - child: SfCartesianChart( - primaryXAxis: CategoryAxis( - title: AxisTitle( - text: 'Weeks - Year', - textStyle: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeSmall, - color: AppSettings.isDarkMode ? Colors.white : Colors.black, - ), - ), - ), - primaryYAxis: NumericAxis( - title: AxisTitle( - text: 'Tasks', - textStyle: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - color: AppSettings.isDarkMode ? Colors.white : Colors.black, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - ), - ), - tooltipBehavior: _weeklyBurndownTooltipBehaviour, - series: [ - ///this is the completed tasks - StackedColumnSeries( - groupName: 'Group A', - enableTooltip: true, - color: TaskWarriorColors.green, - dataSource: allData - .map((task) => ChartData( - 'Week ${Utils.getWeekNumbertoInt(task.entry)}, ${task.entry.year}', - weeklyInfo[Utils.getWeekNumbertoInt(task.entry)] - ?['pending'] ?? - 0, - weeklyInfo[Utils.getWeekNumbertoInt(task.entry)] - ?['completed'] ?? - 0, - )) - .toList(), - xValueMapper: (ChartData data, _) => data.x, - yValueMapper: (ChartData data, _) => data.y2, - name: 'Completed', - ), - - ///this is the pending tasks - StackedColumnSeries( - groupName: 'Group A', - color: TaskWarriorColors.yellow, - enableTooltip: true, - dataSource: allData - .map((task) => ChartData( - 'Week ${Utils.getWeekNumbertoInt(task.entry)}, ${task.entry.year}', - weeklyInfo[Utils.getWeekNumbertoInt(task.entry)] - ?['pending'] ?? - 0, - weeklyInfo[Utils.getWeekNumbertoInt(task.entry)] - ?['completed'] ?? - 0, - )) - .toList(), - xValueMapper: (ChartData data, _) => data.x, - yValueMapper: (ChartData data, _) => data.y1, - name: 'Pending', - ), - ], - ), - ), - ), - const CommonChartIndicator( - title: 'Weekly Burndown Chart', - ), - ], - ); - } -} diff --git a/lib/views/reports/reports_home.dart b/lib/views/reports/reports_home.dart deleted file mode 100644 index 0cd428a6..00000000 --- a/lib/views/reports/reports_home.dart +++ /dev/null @@ -1,228 +0,0 @@ -// ignore_for_file: prefer_typing_uninitialized_variables - -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/controller/reports_tour_controller.dart'; -import 'package:taskwarrior/model/json/task.dart'; -import 'package:taskwarrior/model/storage.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/views/home/home.dart'; -import 'package:taskwarrior/views/reports/pages/burndown_daily.dart'; -import 'package:taskwarrior/views/reports/pages/burndown_monthly.dart'; -import 'package:taskwarrior/views/reports/pages/burndown_weekly.dart'; -import 'package:taskwarrior/views/reports/reports_tour.dart'; -import 'package:taskwarrior/widgets/taskdetails/profiles_widget.dart'; -import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; - -class ReportsHome extends StatefulWidget { - const ReportsHome({ - super.key, - }); - - @override - State createState() => _ReportsHomeState(); -} - -class _ReportsHomeState extends State - with TickerProviderStateMixin { - late TabController _tabController; - final GlobalKey daily = GlobalKey(); - final GlobalKey weekly = GlobalKey(); - final GlobalKey monthly = GlobalKey(); - - bool isSaved = false; - late TutorialCoachMark tutorialCoachMark; - - int _selectedIndex = 0; - var storageWidget; - late Storage storage; - late final Filters filters; - - Directory? baseDirectory; - List allData = []; - - void _initReportsTour() { - tutorialCoachMark = TutorialCoachMark( - targets: reportsDrawer( - daily: daily, - weekly: weekly, - monthly: monthly, - ), - colorShadow: TaskWarriorColors.black, - paddingFocus: 10, - opacityShadow: 0.8, - hideSkip: true, - onFinish: () { - SaveReportsTour().saveReportsTourStatus(); - }, - ); - } - - void _showReportsTour() { - Future.delayed( - const Duration(seconds: 2), - () { - SaveReportsTour().getReportsTourStatus().then((value) => { - if (value == false) - { - tutorialCoachMark.show(context: context), - } - else - { - // ignore: avoid_print - print('User has seen this page'), - } - }); - }, - ); - } - - @override - void initState() { - super.initState(); - _initReportsTour(); - _showReportsTour(); - - _tabController = TabController(length: 3, vsync: this); - - ///initialize the storage widget - Future.delayed(Duration.zero, () { - storageWidget = StorageWidget.of(context); - var currentProfile = ProfilesWidget.of(context).currentProfile; - - Directory baseDirectory = ProfilesWidget.of(context).getBaseDirectory(); - setState(() { - storage = Storage( - Directory('${baseDirectory.path}/profiles/$currentProfile'), - ); - }); - - ///fetch all data contains all the tasks - allData = storage.data.allData(); - }); - } - - @override - Widget build(BuildContext context) { - double height = MediaQuery.of(context).size.height; // Screen height - // double width = MediaQuery.of(context).size.width; // Screen width - - return Scaffold( - appBar: AppBar( - backgroundColor: TaskWarriorColors.kprimaryBackgroundColor, - title: Text( - 'Reports', - style: GoogleFonts.poppins(color: TaskWarriorColors.white), - ), - leading: GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: Icon( - Icons.chevron_left, - color: TaskWarriorColors.white, - ), - ), - bottom: PreferredSize( - preferredSize: Size.fromHeight( - height * 0.1), // Adjust the preferred height as needed - child: TabBar( - controller: _tabController, - labelColor: TaskWarriorColors.white, - labelStyle: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.medium, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - unselectedLabelStyle: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.light, - ), - onTap: (value) { - setState(() { - _selectedIndex = value; - }); - }, - tabs: [ - Tab( - key: daily, - icon: const Icon(Icons.schedule), - text: 'Daily', - iconMargin: const EdgeInsets.only(bottom: 0.0), - ), - Tab( - key: weekly, - icon: const Icon(Icons.today), - text: 'Weekly', - iconMargin: const EdgeInsets.only(bottom: 0.0), - ), - Tab( - key: monthly, - icon: const Icon(Icons.date_range), - text: 'Monthly', - iconMargin: const EdgeInsets.only(bottom: 0.0), - ), - ], - ), - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.white, - body: allData.isEmpty - ? Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon( - Icons.heart_broken, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'No Task found', - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.medium, - fontSize: TaskWarriorFonts.fontSizeSmall, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Add a task to see reports', - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.light, - fontSize: TaskWarriorFonts.fontSizeSmall, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ], - ), - ], - ) - : IndexedStack( - index: _selectedIndex, - children: const [ - BurnDownDaily(), - BurnDownWeekly(), - BurnDownMonthlt(), - ], - ), - ); - } -} diff --git a/lib/views/settings/settings.dart b/lib/views/settings/settings.dart deleted file mode 100644 index 1fae65d2..00000000 --- a/lib/views/settings/settings.dart +++ /dev/null @@ -1,531 +0,0 @@ -// ignore_for_file: library_private_types_in_public_api, must_be_immutable, depend_on_referenced_packages - -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:path/path.dart' as path; -import 'package:shared_preferences/shared_preferences.dart'; -import "package:file_picker/file_picker.dart"; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/utility/utilities.dart'; -import 'package:taskwarrior/widgets/pallete.dart'; -import 'package:taskwarrior/widgets/taskdetails/profiles_widget.dart'; - -class SettingsPage extends StatefulWidget { - SettingsPage({ - super.key, - required this.isSyncOnStartActivel, - required this.isSyncOnTaskCreateActivel, - required this.delaytask, - required this.change24hr, - }); - bool isSyncOnStartActivel; - bool isSyncOnTaskCreateActivel; - bool delaytask; - bool change24hr; - - @override - _SettingsPageState createState() => _SettingsPageState(); -} - -class _SettingsPageState extends State { - bool isMovingDirectory = false; - - String getBaseDirectory() { - InheritedProfiles profilesWidget = ProfilesWidget.of(context); - Directory baseDirectory = profilesWidget.getBaseDirectory(); - Directory defaultDirectory = profilesWidget.getDefaultDirectory(); - if (baseDirectory.path == defaultDirectory.path) { - return 'Default'; - } else { - return baseDirectory.path; - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - centerTitle: false, - backgroundColor: Palette.kToDark.shade200, - title: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Settings', - style: GoogleFonts.poppins( - color: TaskWarriorColors.white, - fontSize: TaskWarriorFonts.fontSizeLarge, - ), - ), - Text( - 'Configure your preferences', - style: GoogleFonts.poppins( - color: TaskWarriorColors.white, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - ), - ], - ), - leading: GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: Icon( - Icons.chevron_left, - color: TaskWarriorColors.white, - ), - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.white, - body: (isMovingDirectory) - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox( - height: 10, - ), - Text( - 'Moving data to new directory', - style: GoogleFonts.poppins( - fontWeight: FontWeight.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ) - ], - )) - : ListView( - children: [ - ListTile( - title: Text( - 'Sync on Start', - style: GoogleFonts.poppins( - fontWeight: FontWeight.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - subtitle: Text( - 'Automatically sync data on app startup', - style: GoogleFonts.poppins( - color: TaskWarriorColors.grey, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - ), - trailing: Switch( - value: widget.isSyncOnStartActivel, - onChanged: (bool value) async { - setState(() { - widget.isSyncOnStartActivel = value; - }); - - final SharedPreferences prefs = - await SharedPreferences.getInstance(); - await prefs.setBool('sync-onStart', value); - }, - ), - ), - const Divider(), - ListTile( - title: Text( - 'Sync on Task Create', - style: GoogleFonts.poppins( - fontWeight: FontWeight.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - subtitle: Text( - 'Enable automatic syncing when creating a new task', - style: GoogleFonts.poppins( - color: TaskWarriorColors.grey, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - ), - trailing: Switch( - value: widget.isSyncOnTaskCreateActivel, - onChanged: (bool value) async { - setState(() { - widget.isSyncOnTaskCreateActivel = value; - }); - - final SharedPreferences prefs = - await SharedPreferences.getInstance(); - await prefs.setBool('sync-OnTaskCreate', value); - }, - ), - ), - const Divider(), - ListTile( - title: Text( - 'Highlight the task', - style: GoogleFonts.poppins( - fontWeight: FontWeight.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - subtitle: Text( - 'Make the border of task if only one day left', - style: GoogleFonts.poppins( - color: TaskWarriorColors.grey, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - ), - trailing: Switch( - value: widget.delaytask, - onChanged: (bool value) async { - setState(() { - widget.delaytask = value; - }); - - final SharedPreferences prefs = - await SharedPreferences.getInstance(); - await prefs.setBool('delaytask', value); - }, - ), - ), - const Divider(), - ListTile( - title: Text( - 'Select directory', - style: GoogleFonts.poppins( - fontWeight: FontWeight.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - subtitle: Column( - children: [ - Text( - 'Select the directory where the TaskWarrior data is stored\nCurrent directory: ${getBaseDirectory()}', - style: GoogleFonts.poppins( - color: TaskWarriorColors.grey, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - ), - const SizedBox( - height: 10, - ), - Row( - children: [ - //Reset to default - TextButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - AppSettings.isDarkMode - ? TaskWarriorColors - .ksecondaryBackgroundColor - : TaskWarriorColors - .kLightSecondaryBackgroundColor, - ), - ), - onPressed: () { - if (getBaseDirectory() == "Default") { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - 'Already default', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors - .kprimaryTextColor - : TaskWarriorColors - .kLightPrimaryTextColor, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors - .ksecondaryBackgroundColor - : TaskWarriorColors - .kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } else { - showDialog( - context: context, - builder: (BuildContext context) { - return Utils.showAlertDialog( - title: Text( - 'Reset to default', - style: GoogleFonts.poppins( - fontWeight: FontWeight.bold, - fontSize: - TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - content: Text( - "Are you sure you want to reset the directory to the default?", - style: GoogleFonts.poppins( - color: TaskWarriorColors.grey, - fontSize: - TaskWarriorFonts.fontSizeMedium, - ), - ), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: Text( - 'No', - style: GoogleFonts.poppins( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ), - TextButton( - onPressed: () { - Navigator.pop(context); - isMovingDirectory = true; - setState(() {}); - InheritedProfiles profilesWidget = - ProfilesWidget.of(context); - Directory source = profilesWidget - .getBaseDirectory(); - Directory destination = - profilesWidget - .getDefaultDirectory(); - moveDirectory(source.path, - destination.path) - .then((value) async { - profilesWidget.setBaseDirectory( - destination); - SharedPreferences prefs = - await SharedPreferences - .getInstance(); - await prefs - .remove('baseDirectory'); - isMovingDirectory = false; - setState(() {}); - }); - }, - child: Text( - 'Yes', - style: GoogleFonts.poppins( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ), - ], - ); - }, - ); - } - }, - child: Text( - 'Reset to default', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.deepPurple, - ), - ), - ), - const Spacer(), - //Change directory - TextButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - AppSettings.isDarkMode - ? TaskWarriorColors - .ksecondaryBackgroundColor - : TaskWarriorColors - .kLightSecondaryBackgroundColor, - ), - ), - onPressed: () { - pickDirectory(); - }, - child: Text( - 'Change directory', - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.deepPurple, - ), - ), - ), - ], - ), - ], - ), - ), - const Divider(), - ListTile( - title: Text( - 'Enable 24HR format', - style: GoogleFonts.poppins( - fontWeight: FontWeight.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - subtitle: Text( - 'Switch to Right to convert in 24hr format', - style: GoogleFonts.poppins( - color: TaskWarriorColors.grey, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - ), - trailing: Switch( - value: widget.change24hr, - onChanged: (bool value) async { - setState(() { - widget.change24hr = value; - }); - - final SharedPreferences prefs = - await SharedPreferences.getInstance(); - await prefs.setBool('24hourformate', value); - }, - ), - ), - ], - ), - ); - } - - void pickDirectory() { - FilePicker.platform.getDirectoryPath().then((value) async { - if (value != null) { - isMovingDirectory = true; - setState(() {}); - InheritedProfiles profilesWidget = ProfilesWidget.of(context); - Directory source = profilesWidget.getBaseDirectory(); - Directory destination = Directory(value); - moveDirectory(source.path, destination.path).then((value) async { - isMovingDirectory = false; - setState(() {}); - if (value == "same") { - return; - } else if (value == "success") { - profilesWidget.setBaseDirectory(destination); - SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.setString('baseDirectory', destination.path); - } else { - showDialog( - context: context, - builder: (BuildContext context) { - return Utils.showAlertDialog( - title: Text( - 'Error', - style: GoogleFonts.poppins( - fontWeight: FontWeight.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - content: Text( - value == "nested" - ? "Cannot move to a nested directory" - : value == "not-empty" - ? "Destination directory is not empty" - : "An error occurred", - style: GoogleFonts.poppins( - color: TaskWarriorColors.grey, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - ), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: Text( - 'OK', - style: GoogleFonts.poppins( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ) - ], - ); - }, - ); - } - }); - } - }); - } - - Future moveDirectory(String fromDirectory, String toDirectory) async { - if (path.canonicalize(fromDirectory) == path.canonicalize(toDirectory)) { - return "same"; - } - - if (path.isWithin(fromDirectory, toDirectory)) { - return "nested"; - } - - Directory toDir = Directory(toDirectory); - final length = await toDir.list().length; - if (length > 0) { - return "not-empty"; - } - - await moveDirectoryRecurse(fromDirectory, toDirectory); - return "success"; - } - - Future moveDirectoryRecurse( - String fromDirectory, String toDirectory) async { - Directory fromDir = Directory(fromDirectory); - Directory toDir = Directory(toDirectory); - - // Create the toDirectory if it doesn't exist - await toDir.create(recursive: true); - - // Loop through each file and directory and move it to the toDirectory - await for (final entity in fromDir.list()) { - if (entity is File) { - // If it's a file, move it to the toDirectory - File file = entity; - String newPath = path.join( - toDirectory, path.relative(file.path, from: fromDirectory)); - await File(newPath).writeAsBytes(await file.readAsBytes()); - await file.delete(); - } else if (entity is Directory) { - // If it's a directory, create it in the toDirectory and recursively move its contents - Directory dir = entity; - String newPath = path.join( - toDirectory, path.relative(dir.path, from: fromDirectory)); - Directory newDir = Directory(newPath); - await newDir.create(recursive: true); - await moveDirectoryRecurse(dir.path, newPath); - await dir.delete(); - } - } - } -} diff --git a/lib/widgets/app_placeholder.dart b/lib/widgets/app_placeholder.dart deleted file mode 100644 index 2541e809..00000000 --- a/lib/widgets/app_placeholder.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; - -class AppSetupPlaceholder extends StatelessWidget { - const AppSetupPlaceholder({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryBackgroundColor - : TaskWarriorColors.kLightPrimaryBackgroundColor, - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - child: SvgPicture.asset( - "assets/svg/logo.svg", - height: 100, - width: double.infinity, - )), - const SizedBox(height: 30.0), - const CircularProgressIndicator(), - const SizedBox(height: 16.0), - Text( - "Setting up the app...", - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeLarge, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/widgets/createDrawerBodyItem.dart b/lib/widgets/createDrawerBodyItem.dart deleted file mode 100644 index 2984c069..00000000 --- a/lib/widgets/createDrawerBodyItem.dart +++ /dev/null @@ -1,21 +0,0 @@ -// ignore_for_file: prefer_const_constructors, file_names - -import 'package:flutter/material.dart'; - -Widget createDrawerBodyItem( - {required IconData icon, - required String text, - required GestureTapCallback onTap}) { - return ListTile( - title: Row( - children: [ - Icon(icon), - Padding( - padding: EdgeInsets.only(left: 8.0), - child: Text(text), - ) - ], - ), - onTap: onTap, - ); -} diff --git a/lib/widgets/createDrawerHeader.dart b/lib/widgets/createDrawerHeader.dart deleted file mode 100644 index 102a48ab..00000000 --- a/lib/widgets/createDrawerHeader.dart +++ /dev/null @@ -1,21 +0,0 @@ -// ignore_for_file: prefer_const_constructors, file_names - -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; - -Widget createDrawerHeader() { - return ListTile( - contentPadding: EdgeInsets.all(8.0), - leading: CircleAvatar( - backgroundColor: Colors.transparent, - ), - title: Text( - 'Taskwarrior', - style: GoogleFonts.poppins( - fontSize: 30.0, - fontWeight: TaskWarriorFonts.medium, - ), - ), - ); -} diff --git a/lib/widgets/drawer_tile.dart b/lib/widgets/drawer_tile.dart deleted file mode 100644 index 9d8c4ec0..00000000 --- a/lib/widgets/drawer_tile.dart +++ /dev/null @@ -1,18 +0,0 @@ -// ignore_for_file: use_key_in_widget_constructors, must_be_immutable - -import 'package:flutter/material.dart'; - -class DrawerTile extends StatelessWidget { - Widget? title; - void Function()? onTap; - - DrawerTile({required this.title, required this.onTap}); - - @override - Widget build(BuildContext context) { - return ListTile( - title: title, - onTap: onTap, - ); - } -} diff --git a/lib/widgets/fingerprint.dart b/lib/widgets/fingerprint.dart deleted file mode 100644 index 2555ec67..00000000 --- a/lib/widgets/fingerprint.dart +++ /dev/null @@ -1,13 +0,0 @@ -// ignore_for_file: depend_on_referenced_packages - -import 'package:crypto/crypto.dart'; -import 'package:pem/pem.dart'; - -String fingerprint(String pemContents) { - var firstCertificateBlock = decodePemBlocks( - PemLabel.certificate, - pemContents, - ).first; - - return '${sha1.convert(firstCertificateBlock)}'; -} diff --git a/lib/widgets/full_button.dart b/lib/widgets/full_button.dart deleted file mode 100644 index 0391c3bd..00000000 --- a/lib/widgets/full_button.dart +++ /dev/null @@ -1,20 +0,0 @@ -// ignore_for_file: deprecated_member_use, prefer_const_constructors_in_immutables, use_key_in_widget_constructors, must_be_immutable - -import 'package:flutter/material.dart'; - -class FormSubmitButton extends StatelessWidget { - final Function() onPressed; - String type; - FormSubmitButton({required this.onPressed, required this.type}); - - @override - Widget build(BuildContext context) => ElevatedButton( - style: ElevatedButton.styleFrom( - shape: const StadiumBorder(), - // onPrimary: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12), - ), - onPressed: onPressed, - child: Text(type), - ); -} diff --git a/lib/widgets/home_paths.dart b/lib/widgets/home_paths.dart deleted file mode 100644 index 7942b6c6..00000000 --- a/lib/widgets/home_paths.dart +++ /dev/null @@ -1,7 +0,0 @@ -library home_paths; - -export 'home_paths/impl/data.dart'; -export 'home_paths/impl/gui_pem_file_paths.dart'; -export 'home_paths/impl/taskd_client.dart'; -export 'home_paths/impl/taskrc.dart'; -export 'home_paths/home.dart'; diff --git a/lib/widgets/profilefunctions.dart b/lib/widgets/profilefunctions.dart deleted file mode 100644 index 161c122b..00000000 --- a/lib/widgets/profilefunctions.dart +++ /dev/null @@ -1,6 +0,0 @@ -library profilefunctions; - -export 'profilefunctions/manageprofile.dart'; -export 'profilefunctions/renameprofiledialog.dart'; -export 'profilefunctions/selectprofile.dart'; -export 'profilefunctions/deleteprofiledialog.dart'; diff --git a/lib/widgets/project_filter.dart b/lib/widgets/project_filter.dart deleted file mode 100644 index b3b31e05..00000000 --- a/lib/widgets/project_filter.dart +++ /dev/null @@ -1,234 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:google_fonts/google_fonts.dart'; -import 'package:sizer/sizer.dart'; - -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; - -class InheritedProjects extends InheritedWidget { - // ignore: use_super_parameters - const InheritedProjects({ - required this.projects, - required this.projectFilter, - required this.callback, - required child, - super.key, - }) : super(child: child); - - final Map projects; - final String projectFilter; - final void Function(String) callback; - - static InheritedProjects of(BuildContext context) { - return context.dependOnInheritedWidgetOfExactType()!; - } - - @override - bool updateShouldNotify(InheritedProjects oldWidget) => - projectFilter != oldWidget.projectFilter || - projects != oldWidget.projects || - callback != oldWidget.callback; -} - -class ProjectsColumn extends StatelessWidget { - const ProjectsColumn(this.projects, this.projectFilter, this.callback, - {super.key}); - - final Map projects; - final String projectFilter; - final void Function(String) callback; - - @override - Widget build(BuildContext context) { - return InheritedProjects( - projectFilter: projectFilter, - callback: callback, - projects: projects, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - "Project : ", - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - SizedBox( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - projectFilter == "" - ? "Not selected" - : projectFilter, - style: GoogleFonts.poppins( - fontSize: TaskWarriorFonts.fontSizeSmall, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ], - ), - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.only(left: 10, right: 10, top: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "All Projects", - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.semiBold, - fontSize: TaskWarriorFonts.fontSizeSmall, - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - ), - ), - ], - ), - ), - if (projects.isNotEmpty) - ...projects.entries - .where((entry) => entry.value.parent == null) - .map((entry) => ProjectTile(entry.key)) - else - Column( - children: [ - Text( - "No Projects Found", - style: GoogleFonts.poppins( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black, - fontSize: TaskWarriorFonts.fontSizeSmall, - ), - ), - SizedBox( - height: 2.h, - ), - ], - ), - ], - )); - } -} - -class ProjectTile extends StatelessWidget { - const ProjectTile(this.project, {super.key}); - - final String project; - - @override - Widget build(BuildContext context) { - var inheritedProjects = InheritedProjects.of(context); - - var node = inheritedProjects.projects[project]!; - var projectFilter = inheritedProjects.projectFilter; - var callback = inheritedProjects.callback; - - var title = Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Flexible( - child: Text(project, - style: GoogleFonts.poppins( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black))), - Text( - (node.children.isEmpty) - ? '${node.subtasks}' - : '(${node.tasks}) ${node.subtasks}', - style: GoogleFonts.poppins( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black), - ) - ], - ); - - var radio = Radio( - activeColor: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.ksecondaryBackgroundColor, - focusColor: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.ksecondaryBackgroundColor, - toggleable: true, - value: project, - groupValue: projectFilter, - onChanged: (_) => callback(project), - ); - - return (node.children.isEmpty) - ? GestureDetector( - onTap: () => callback(project), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - radio, - Text(project, - maxLines: 3, - style: GoogleFonts.poppins( - color: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.black)), - const Spacer(), - Container( - padding: const EdgeInsets.symmetric(horizontal: 4), - margin: const EdgeInsets.only(right: 10), - decoration: BoxDecoration( - color: (AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.ksecondaryBackgroundColor), - ), - child: Text( - (node.children.isEmpty) - ? '${node.subtasks}' - : '(${node.tasks}) ${node.subtasks}', - maxLines: 1, - style: GoogleFonts.poppins( - color: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white), - ), - ), - ], - ), - ) - : ExpansionTile( - controlAffinity: ListTileControlAffinity.leading, - key: PageStorageKey(project), - leading: radio, - title: title, - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.ksecondaryBackgroundColor, - textColor: AppSettings.isDarkMode - ? TaskWarriorColors.white - : TaskWarriorColors.ksecondaryBackgroundColor, - children: node.children.map(ProjectTile.new).toList(), - ); - } -} diff --git a/lib/widgets/tag_filter.dart b/lib/widgets/tag_filter.dart deleted file mode 100644 index 2402fe18..00000000 --- a/lib/widgets/tag_filter.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:google_fonts/google_fonts.dart'; - -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; - -class TagFilterMetadata { - const TagFilterMetadata({ - required this.display, - required this.selected, - }); - - final String display; - final bool selected; -} - -class TagFilters { - const TagFilters({ - required this.tagUnion, - required this.toggleTagUnion, - required this.tags, - required this.toggleTagFilter, - }); - - final bool tagUnion; - final void Function() toggleTagUnion; - final Map tags; - final void Function(String) toggleTagFilter; -} - -class TagFiltersWrap extends StatelessWidget { - const TagFiltersWrap(this.filters, {super.key}); - - final TagFilters filters; - - @override - Widget build(BuildContext context) { - return Wrap( - spacing: 4, - children: [ - FilterChip( - onSelected: (_) => filters.toggleTagUnion(), - label: Text(filters.tagUnion ? 'OR' : 'AND', - style: GoogleFonts.poppins( - color: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white)), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kLightSecondaryBackgroundColor - : TaskWarriorColors.ksecondaryBackgroundColor, - ), - for (var entry in filters.tags.entries) - FilterChip( - onSelected: (_) => filters.toggleTagFilter(entry.key), - label: Text( - entry.value.display, - style: GoogleFonts.poppins( - fontWeight: - entry.value.selected ? TaskWarriorFonts.bold : null, - color: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.white), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.kLightSecondaryBackgroundColor - : TaskWarriorColors.kprimaryBackgroundColor, - ), - ], - ); - } -} diff --git a/lib/widgets/taskc.dart b/lib/widgets/taskc.dart deleted file mode 100644 index 10be15fd..00000000 --- a/lib/widgets/taskc.dart +++ /dev/null @@ -1,7 +0,0 @@ -library taskc; - -export 'taskc/message.dart'; -export 'taskc/payload.dart'; -export 'taskc/response.dart'; -export 'taskc/impl/codec.dart'; -export 'taskc/impl/message.dart'; diff --git a/lib/widgets/taskdetails.dart b/lib/widgets/taskdetails.dart deleted file mode 100644 index 919bf0c1..00000000 --- a/lib/widgets/taskdetails.dart +++ /dev/null @@ -1,8 +0,0 @@ -library taskdetails; - -export 'taskdetails/dateTimePicker.dart'; -export 'taskdetails/priority_widget.dart'; -export 'taskdetails/profiles_widget.dart'; -export 'taskdetails/description_widget.dart'; -export 'taskdetails/status_widget.dart'; -export 'taskdetails/tags_widget.dart'; diff --git a/lib/widgets/taskdetails/dateTimePicker.dart b/lib/widgets/taskdetails/dateTimePicker.dart deleted file mode 100644 index ad31f9a2..00000000 --- a/lib/widgets/taskdetails/dateTimePicker.dart +++ /dev/null @@ -1,196 +0,0 @@ -// ignore_for_file: file_names, use_build_context_synchronously - -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; - -import 'package:intl/intl.dart'; - -import 'package:taskwarrior/config/app_settings.dart'; -import 'package:taskwarrior/config/taskwarriorcolors.dart'; -import 'package:taskwarrior/config/taskwarriorfonts.dart'; - -class DateTimeWidget extends StatelessWidget { - const DateTimeWidget({ - super.key, - required this.name, - required this.value, - required this.callback, - }); - - final String name; - final dynamic value; - final void Function(dynamic) callback; - - @override - Widget build(BuildContext context) { - return Card( - color: AppSettings.isDarkMode - ? const Color.fromARGB(255, 57, 57, 57) - : Colors.white, - child: ListTile( - textColor: AppSettings.isDarkMode - ? Colors.white - : const Color.fromARGB(255, 48, 46, 46), - title: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - RichText( - text: TextSpan( - children: [ - TextSpan( - text: '$name:'.padRight(13), - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? Colors.white - : Colors.black, - ), - ), - TextSpan( - text: value ?? "not selected", - style: GoogleFonts.poppins( - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? Colors.white - : Colors.black, - ), - ), - ], - ), - ), - ], - ), - ), - onTap: () async { - var initialDate = DateFormat("E, M/d/y h:mm:ss a").parse( - value?.replaceAll(RegExp(r'\s+'), ' ') ?? - DateFormat("E, M/d/y h:mm:ss a").format(DateTime.now())); - - var date = await showDatePicker( - context: context, - initialDate: initialDate, - firstDate: DateTime - .now(), // sets the earliest selectable date to the current date. This prevents the user from selecting a date in the past. - lastDate: DateTime(2037, 12, 31), // < 2038-01-19T03:14:08.000Z - ); - if (date != null) { - var time = await showTimePicker( - context: context, - initialTime: TimeOfDay.now(), - ); - if (time != null) { - var dateTime = date.add( - Duration( - hours: time.hour, - minutes: time.minute, - ), - ); - dateTime = dateTime.add( - Duration( - hours: time.hour - dateTime.hour, - ), - ); - // Check if the selected time is in the past - if (dateTime.isBefore(DateTime.now())) { - // Show a message that past times can't be set - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - "Can't set times in the past", - style: TextStyle( - color: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightPrimaryTextColor, - ), - ), - backgroundColor: AppSettings.isDarkMode - ? TaskWarriorColors.ksecondaryBackgroundColor - : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } else { - // If the time is not in the past, proceed as usual - return callback(dateTime.toUtc()); - } - } - } - }, - onLongPress: () => callback(null), - ), - ); - } -} - -class StartWidget extends StatelessWidget { - const StartWidget({ - required this.name, - required this.value, - required this.callback, - super.key, - }); - - final String name; - final dynamic value; - final void Function(dynamic) callback; - - @override - Widget build(BuildContext context) { - return Card( - color: AppSettings.isDarkMode - ? const Color.fromARGB(255, 57, 57, 57) - : Colors.white, - child: ListTile( - textColor: AppSettings.isDarkMode - ? Colors.white - : const Color.fromARGB(255, 48, 46, 46), - title: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - RichText( - text: TextSpan( - children: [ - TextSpan( - text: '$name:'.padRight(13), - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? Colors.white - : Colors.black, - ), - ), - TextSpan( - text: value ?? "not selected", - style: GoogleFonts.poppins( - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? Colors.white - : Colors.black, - ), - ), - ], - ), - ), - ], - ), - ), - onTap: () { - if (value != null) { - callback(null); - } else { - var now = DateTime.now().toUtc(); - callback(DateTime.utc( - now.year, - now.month, - now.day, - now.hour, - now.minute, - now.second, - )); - } - }, - ), - ); - } -} diff --git a/lib/widgets/taskdetails/profiles_widget.dart b/lib/widgets/taskdetails/profiles_widget.dart deleted file mode 100644 index e5b78e2e..00000000 --- a/lib/widgets/taskdetails/profiles_widget.dart +++ /dev/null @@ -1,181 +0,0 @@ -// ignore_for_file: use_super_parameters - -import 'dart:io'; - -import 'package:flutter/material.dart'; - -import 'package:taskwarrior/model/storage.dart'; -import 'package:taskwarrior/model/storage/storage_widget.dart'; -import 'package:taskwarrior/widgets/taskw.dart'; - -//import 'package:taskc/storage.dart'; - -// import 'package:task/task.dart'; - -class ProfilesWidget extends StatefulWidget { - const ProfilesWidget({ - Key? key, - required this.defaultDirectory, - required this.baseDirectory, - required this.child, - }) : super(key: key); - - final Directory defaultDirectory; - final Directory baseDirectory; - final Widget child; - - @override - State createState() => _ProfilesWidgetState(); - - static InheritedProfiles of(BuildContext context) { - return InheritedModel.inheritFrom(context)!; - } -} - -class _ProfilesWidgetState extends State { - late Directory baseDirectory; - late Map profilesMap; - late String currentProfile; - - Profiles get _profiles => Profiles(baseDirectory); - - @override - void initState() { - super.initState(); - baseDirectory = widget.baseDirectory; - _checkProfiles(); - profilesMap = _profiles.profilesMap(); - currentProfile = _profiles.getCurrentProfile()!; - setState(() {}); - } - - void _checkProfiles() { - if (_profiles.profilesMap().isEmpty) { - _profiles.setCurrentProfile(_profiles.addProfile()); - } else if (!_profiles - .profilesMap() - .containsKey(_profiles.getCurrentProfile())) { - _profiles.setCurrentProfile(_profiles.profilesMap().keys.first); - } - } - - Directory getDefaultDirectory() { - return widget.defaultDirectory; - } - - Directory getBaseDirectory() { - return baseDirectory; - } - - void setBaseDirectory(Directory newBaseDirectory) { - baseDirectory = newBaseDirectory; - profilesMap = _profiles.profilesMap(); - setState(() {}); - } - - void addProfile() { - _profiles.addProfile(); - profilesMap = _profiles.profilesMap(); - setState(() {}); - } - - void copyConfigToNewProfile(String profile) { - _profiles.copyConfigToNewProfile(profile); - profilesMap = _profiles.profilesMap(); - setState(() {}); - } - - void deleteProfile(String profile) { - _profiles.deleteProfile(profile); - _checkProfiles(); - profilesMap = _profiles.profilesMap(); - currentProfile = _profiles.getCurrentProfile()!; - setState(() {}); - } - - void renameProfile({required String profile, required String? alias}) { - _profiles.setAlias(profile: profile, alias: alias!); - profilesMap = _profiles.profilesMap(); - setState(() {}); - } - - void selectProfile(String profile) { - _profiles.setCurrentProfile(profile); - currentProfile = _profiles.getCurrentProfile()!; - setState(() {}); - } - - Storage getStorage(String profile) { - return _profiles.getStorage(profile); - } - - @override - Widget build(BuildContext context) { - return InheritedProfiles( - getDefaultDirectory: getDefaultDirectory, - getBaseDirectory: getBaseDirectory, - setBaseDirectory: setBaseDirectory, - addProfile: addProfile, - copyConfigToNewProfile: copyConfigToNewProfile, - deleteProfile: deleteProfile, - renameProfile: renameProfile, - selectProfile: selectProfile, - currentProfile: currentProfile, - profilesMap: profilesMap, - getStorage: getStorage, - setState: setState, - child: StorageWidget( - profile: Directory( - '${baseDirectory.path}/profiles/$currentProfile', - ), - child: widget.child, - ), - ); - } -} - -class InheritedProfiles extends InheritedModel { - const InheritedProfiles({ - super.key, - required this.getDefaultDirectory, - required this.getBaseDirectory, - required this.setBaseDirectory, - required this.addProfile, - required this.copyConfigToNewProfile, - required this.deleteProfile, - required this.renameProfile, - required this.selectProfile, - required this.currentProfile, - required this.profilesMap, - required this.getStorage, - required this.setState, - required child, - }) : super(child: child); - - final Directory Function() getDefaultDirectory; - final Directory Function() getBaseDirectory; - final void Function(Directory) setBaseDirectory; - final Function() addProfile; - final Function(String) copyConfigToNewProfile; - final Function(String) deleteProfile; - final void Function({ - required String profile, - required String? alias, - }) renameProfile; - final Function(String) selectProfile; - final String currentProfile; - final Map profilesMap; - final Storage Function(String) getStorage; - final void Function(void Function()) setState; - - @override - bool updateShouldNotify(InheritedProfiles oldWidget) { - return true; - } - - @override - bool updateShouldNotifyDependent( - InheritedProfiles oldWidget, Set dependencies) { - return true; - } -} diff --git a/lib/widgets/taskserver.dart b/lib/widgets/taskserver.dart deleted file mode 100644 index 1b5214e4..00000000 --- a/lib/widgets/taskserver.dart +++ /dev/null @@ -1,8 +0,0 @@ -library taskserver; - -export 'taskserver/credentials.dart'; -export 'taskserver/parse_taskrc.dart'; -export 'taskserver/pem_file_paths.dart'; -export 'taskserver/server.dart'; -export 'taskserver/taskrc.dart'; -export 'taskserver/taskrc_exception.dart'; diff --git a/lib/widgets/taskw.dart b/lib/widgets/taskw.dart deleted file mode 100644 index 695338da..00000000 --- a/lib/widgets/taskw.dart +++ /dev/null @@ -1,17 +0,0 @@ -/// Currently this library contains code that depends on neither the Flutter -/// sdk, nor the taskd executable to run tests. - -library taskw; - -export 'taskfunctions/comparator.dart'; -export 'taskfunctions/draft.dart'; -export 'taskfunctions/datetime_differences.dart'; -export 'taskfunctions/modify.dart'; -export 'taskfunctions/patch.dart'; -export 'taskfunctions/profiles.dart'; -export 'taskfunctions/projects.dart'; -export 'taskfunctions/query.dart'; -export 'taskfunctions/tags.dart'; -export 'taskfunctions/taskparser.dart'; -export 'taskfunctions/urgency.dart'; -export 'taskfunctions/validate.dart'; diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 00000000..3e5728a9 --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "taskwarrior") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.ccextractor.taskwarrior") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +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() + +# Define build configuration 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. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +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 "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +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}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +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) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# 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() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +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} +) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..7299b5cf --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,19 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..786ff5c2 --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_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 $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/linux/main.cc b/linux/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/linux/main.cc @@ -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); +} diff --git a/linux/my_application.cc b/linux/my_application.cc new file mode 100644 index 00000000..6ce40ec6 --- /dev/null +++ b/linux/my_application.cc @@ -0,0 +1,124 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#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, "taskwarrior"); + 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, "taskwarrior"); + } + + 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 GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// 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_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + 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)); +} diff --git a/linux/my_application.h b/linux/my_application.h new file mode 100644 index 00000000..72271d5e --- /dev/null +++ b/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +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_ diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 833e5775..1d0b75df 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -18,7 +18,7 @@ PODS: - FlutterMacOS - permission_handler_apple (0.0.1): - FlutterMacOS - - ReachabilitySwift (5.0.0) + - ReachabilitySwift (5.2.3) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -74,9 +74,9 @@ SPEC CHECKSUMS: flutter_native_timezone: 3a4724189c47dea215bb3e168e555e18308d312c FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce - path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: fa7012038b5aa4ed34436aa5aa4e943f94b32417 - ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index e6ea7548..0c0079a0 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -21,14 +21,14 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 27C525DBC73356ACD42A9E53 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A10071A838AFFDE1DC6CFA82 /* Pods_Runner.framework */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - 3C28F85C73F0954E92217847 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B66B08AE0997A20094424E90 /* Pods_RunnerTests.framework */; }; - F38E75515C6318D162BC4F4F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FC9FB5A43CE8DADEB93105D /* Pods_Runner.framework */; }; + DC4B7B83B7803BEDE7B2678D /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08D91DE580BDB610D00D5E59 /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -62,11 +62,13 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 08D91DE580BDB610D00D5E59 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 215CF82755E4CD0770153392 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* taskwarriorflutter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = taskwarriorflutter.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* taskwarrior.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = taskwarrior.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -78,16 +80,14 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 4B9D0F6A1A347726A5468ECC /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 5FC9FB5A43CE8DADEB93105D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 6A0E3991D2B304BA78D2D9AD /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - 778DE529EF5058D118C3DAAC /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 4DDCC88C0E1D6F9D445CE756 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 52FE70A48FE42CBA88069E64 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 8DD06E6598B49F18D691C166 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - B66B08AE0997A20094424E90 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - BE199DA6B5823F63FD4A5226 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - E23D159C6E39B903CE5D2B10 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 9A5FCE63F67458C3E82D5AFC /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + A10071A838AFFDE1DC6CFA82 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BB7100FD5D8A8E4062128BB2 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + D476CD08624C6C7F31DB3D9B /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -95,7 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3C28F85C73F0954E92217847 /* Pods_RunnerTests.framework in Frameworks */, + DC4B7B83B7803BEDE7B2678D /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -103,27 +103,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F38E75515C6318D162BC4F4F /* Pods_Runner.framework in Frameworks */, + 27C525DBC73356ACD42A9E53 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 0EF37E389A60FE96C438BA82 /* Pods */ = { - isa = PBXGroup; - children = ( - BE199DA6B5823F63FD4A5226 /* Pods-Runner.debug.xcconfig */, - 8DD06E6598B49F18D691C166 /* Pods-Runner.release.xcconfig */, - 778DE529EF5058D118C3DAAC /* Pods-Runner.profile.xcconfig */, - 6A0E3991D2B304BA78D2D9AD /* Pods-RunnerTests.debug.xcconfig */, - 4B9D0F6A1A347726A5468ECC /* Pods-RunnerTests.release.xcconfig */, - E23D159C6E39B903CE5D2B10 /* Pods-RunnerTests.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; 331C80D6294CF71000263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( @@ -151,14 +137,14 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, - 0EF37E389A60FE96C438BA82 /* Pods */, + 4446E564A3B9906D8A03DAD4 /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* taskwarriorflutter.app */, + 33CC10ED2044A3C60003C045 /* taskwarrior.app */, 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; @@ -199,11 +185,25 @@ path = Runner; sourceTree = ""; }; + 4446E564A3B9906D8A03DAD4 /* Pods */ = { + isa = PBXGroup; + children = ( + 4DDCC88C0E1D6F9D445CE756 /* Pods-Runner.debug.xcconfig */, + 215CF82755E4CD0770153392 /* Pods-Runner.release.xcconfig */, + 9A5FCE63F67458C3E82D5AFC /* Pods-Runner.profile.xcconfig */, + 52FE70A48FE42CBA88069E64 /* Pods-RunnerTests.debug.xcconfig */, + BB7100FD5D8A8E4062128BB2 /* Pods-RunnerTests.release.xcconfig */, + D476CD08624C6C7F31DB3D9B /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( - 5FC9FB5A43CE8DADEB93105D /* Pods_Runner.framework */, - B66B08AE0997A20094424E90 /* Pods_RunnerTests.framework */, + A10071A838AFFDE1DC6CFA82 /* Pods_Runner.framework */, + 08D91DE580BDB610D00D5E59 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -215,7 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - DCC4966A050A74D5B878E5D9 /* [CP] Check Pods Manifest.lock */, + 8B39648B4370CE53C407CDDE /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -234,13 +234,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 8B30488A27B6BF83378E55CA /* [CP] Check Pods Manifest.lock */, + 32602EC495716637807DE574 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - 70403CC254FC200AE98F2D9C /* [CP] Embed Pods Frameworks */, + 57127D9BFB21B6F0C92D052B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -249,7 +249,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* taskwarriorflutter.app */; + productReference = 33CC10ED2044A3C60003C045 /* taskwarrior.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -258,8 +258,9 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { @@ -322,6 +323,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 32602EC495716637807DE574 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -360,7 +383,7 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - 70403CC254FC200AE98F2D9C /* [CP] Embed Pods Frameworks */ = { + 57127D9BFB21B6F0C92D052B /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -377,29 +400,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 8B30488A27B6BF83378E55CA /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - DCC4966A050A74D5B878E5D9 /* [CP] Check Pods Manifest.lock */ = { + 8B39648B4370CE53C407CDDE /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -472,46 +473,46 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6A0E3991D2B304BA78D2D9AD /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 52FE70A48FE42CBA88069E64 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarrior.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/taskwarriorflutter.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/taskwarriorflutter"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/taskwarrior.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/taskwarrior"; }; name = Debug; }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4B9D0F6A1A347726A5468ECC /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = BB7100FD5D8A8E4062128BB2 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarrior.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/taskwarriorflutter.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/taskwarriorflutter"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/taskwarrior.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/taskwarrior"; }; name = Release; }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E23D159C6E39B903CE5D2B10 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = D476CD08624C6C7F31DB3D9B /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarrior.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/taskwarriorflutter.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/taskwarriorflutter"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/taskwarrior.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/taskwarrior"; }; name = Profile; }; @@ -520,6 +521,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -543,9 +545,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -593,6 +597,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -616,9 +621,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -646,6 +653,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -669,9 +677,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 368403f8..08885156 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ @@ -31,7 +31,7 @@ @@ -65,7 +65,7 @@ @@ -82,7 +82,7 @@ diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig index 2edfefa7..4a8c5a8d 100644 --- a/macos/Runner/Configs/AppInfo.xcconfig +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = taskwarriorflutter +PRODUCT_NAME = taskwarrior // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter +PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarrior // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2024 com.ccextractor. All rights reserved. diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index c946719a..dbb44cbc 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -2,13 +2,15 @@ + com.apple.security.network.client + com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server - com.apple.security.network.client - + com.apple.security.files.user-selected.read-write + diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 48271acc..852fa1a4 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -4,7 +4,5 @@ com.apple.security.app-sandbox - com.apple.security.network.client - diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift index 5418c9f5..61f3bd1f 100644 --- a/macos/RunnerTests/RunnerTests.swift +++ b/macos/RunnerTests/RunnerTests.swift @@ -1,5 +1,5 @@ -import FlutterMacOS import Cocoa +import FlutterMacOS import XCTest class RunnerTests: XCTestCase { diff --git a/pubspec.lock b/pubspec.lock index e9e00841..c8a884d7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -169,6 +169,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + color: + dependency: transitive + description: + name: color + sha256: ddcdf1b3badd7008233f5acffaf20ca9f5dc2cd0172b75f68f24526a5f5725cb + url: "https://pub.dev" + source: hosted + version: "3.0.0" connectivity_plus: dependency: "direct main" description: @@ -233,6 +241,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.4" + dartx: + dependency: transitive + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" date_format: dependency: "direct main" description: @@ -374,6 +390,30 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_gen: + dependency: "direct main" + description: + name: flutter_gen + sha256: a518a4a319511346ace92e550e14df35797f15d56ff1c79dc481c069d063259b + url: "https://pub.dev" + source: hosted + version: "5.5.0+1" + flutter_gen_core: + dependency: transitive + description: + name: flutter_gen_core + sha256: b9894396b2a790cc2d6eb3ed86e5e113aaed993765b21d4b981c9da4476e0f52 + url: "https://pub.dev" + source: hosted + version: "5.5.0+1" + flutter_gen_runner: + dependency: "direct dev" + description: + name: flutter_gen_runner + sha256: b4c4c54e4dd89022f5e405fe96f16781be2dfbeabe8a70ccdf73b7af1302c655 + url: "https://pub.dev" + source: hosted + version: "5.5.0+1" flutter_launcher_icons: dependency: "direct main" description: @@ -386,10 +426,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "4.0.0" flutter_local_notifications: dependency: "direct main" description: @@ -500,10 +540,10 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: f0b8d115a13ecf827013ec9fc883390ccc0e87a96ed5347a3114cac177ef18e8 + sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.2.1" graphs: dependency: transitive description: @@ -512,6 +552,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + hashcodes: + dependency: transitive + description: + name: hashcodes + sha256: "80f9410a5b3c8e110c4b7604546034749259f5d6dcca63e0d3c17c9258f1a651" + url: "https://pub.dev" + source: hosted + version: "2.0.0" hive: dependency: "direct main" description: @@ -528,22 +576,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - hive_generator: - dependency: "direct dev" - description: - name: hive_generator - sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" - url: "https://pub.dev" - source: hosted - version: "2.0.1" home_widget: dependency: "direct main" description: name: home_widget - sha256: "29f75e25ed94024eccd862c1e7d9e2c6e3b3cbc3e1c919fac042545b26839f8b" + sha256: "2a0fdd6267ff975bd07bedf74686bd5577200f504f5de36527ac1b56bdbe68e3" url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.6.0" html: dependency: transitive description: @@ -584,6 +624,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.3" + image_size_getter: + dependency: transitive + description: + name: image_size_getter + sha256: f98c4246144e9b968899d2dfde69091e22a539bb64bc9b0bea51505fbb490e57 + url: "https://pub.dev" + source: hosted + version: "2.1.3" import_sorter: dependency: "direct main" description: @@ -624,14 +672,38 @@ packages: url: "https://pub.dev" source: hosted version: "4.8.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.0" logging: dependency: transitive description: @@ -652,26 +724,26 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.0" mime: dependency: transitive description: @@ -716,10 +788,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_parsing: dependency: transitive description: @@ -732,10 +804,10 @@ packages: dependency: transitive description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.3" path_provider_android: dependency: transitive description: @@ -748,10 +820,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -978,22 +1050,6 @@ packages: description: flutter source: sdk version: "0.0.99" - source_gen: - dependency: transitive - description: - name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" - url: "https://pub.dev" - source: hosted - version: "1.5.0" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" - url: "https://pub.dev" - source: hosted - version: "1.3.4" source_span: dependency: transitive description: @@ -1078,10 +1134,18 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" + time: + dependency: transitive + description: + name: time + sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221 + url: "https://pub.dev" + source: hosted + version: "2.1.4" timezone: dependency: "direct main" description: @@ -1242,6 +1306,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" watcher: dependency: transitive description: @@ -1299,5 +1371,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.20.0-1.2.pre" diff --git a/pubspec.yaml b/pubspec.yaml index b5ea7119..c0e89b43 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,7 @@ dependencies: file_selector: ^0.9.4 flutter: sdk: flutter + flutter_gen: ^5.4.0 flutter_launcher_icons: ^0.13.1 flutter_local_notifications: ^16.2.0 flutter_native_splash: ^2.1.2+1 @@ -58,49 +59,35 @@ dependencies: uuid: ^4.2.2 dev_dependencies: - build_runner: ^2.1.11 - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^3.0.1 + build_runner: null + flutter_gen_runner: null + flutter_lints: 4.0.0 flutter_test: sdk: flutter - hive_generator: ^2.0.1 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec -# The following section is specific to Flutter. +flutter_gen: + output: lib/app/utils/gen/ + integrations: + flutter_svg: true + flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - # To add assets to your application, add an assets section, like this: assets: - assets/svg/ - assets/images/ - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + fonts: + - family: SFProDisplay + fonts: + - asset: assets/fonts/SFProDisplay-Regular.ttf + - family: SegoeUI + fonts: + - asset: assets/fonts/SegoeUI.ttf + - family: Ubuntu + fonts: + - asset: assets/fonts/Ubuntu-Light.ttf + - family: Poppins + fonts: + - asset: assets/fonts/Poppins-Regular.ttf + - asset: assets/fonts/Poppins-Bold.ttf + - asset: assets/fonts/Poppins-ExtraBold.ttf + weight: 900 diff --git a/test/views/profile/profile_test.dart b/test/views/profile/profile_test.dart deleted file mode 100644 index ea3ebd36..00000000 --- a/test/views/profile/profile_test.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:taskwarrior/views/profile/profile.dart'; - -void main() { - testWidgets('test profiles column widget', (WidgetTester tester) async { - // Pump the ProfilesColumn widget into the widget tree for testing. - await tester.pumpWidget( - MaterialApp( - home: Material( - child: ProfilesColumn( - const {}, // Initial data for profiles (empty in this case). - 'foo', // Current user's ID. - - // Callback for adding a new profile. - () { - // This function is called when the "Add Profile" button is pressed. - // Implement logic to add a new profile here. - }, - - // Callback for selecting a profile. - (String profileId) { - // This function is called when a profile is selected. - // The 'profileId' parameter is the ID of the selected profile. - // Implement logic to handle profile selection here. - }, - - // Callback for renaming a profile. - () { - // This function is called when the "Rename" button is pressed. - // Implement logic to rename the selected profile here. - }, - - // Callback for configuring a profile. - () { - // This function is called when the "Configure" button is pressed. - // Implement logic to configure the selected profile here. - }, - - // Callback for exporting a profile. - () { - // This function is called when the "Export" button is pressed. - // Implement logic to export the selected profile here. - }, - - // Callback for copying a profile. - () { - // This function is called when the "Copy" button is pressed. - // Implement logic to copy the selected profile here. - }, - - // Callback for deleting a profile. - () { - // This function is called when the "Delete" button is pressed. - // Implement logic to delete the selected profile here. - }, - ), - ), - ), - ); - }); -} diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index c2eadc97..00000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:taskwarrior/model/json/task.dart'; -import 'package:taskwarrior/services/task_list_tem.dart'; - -import 'package:uuid/uuid.dart'; - -void main() { - // This test checks if there are tasks in the app. - testWidgets("check if there are tasks in the app", - (WidgetTester tester) async { - // Pump the widget tree and await its rendering. - await tester.pumpWidget( - MaterialApp( - // Create a Material app with the specified home widget. - home: Material( - child: TaskListItem( - // Create a TaskListItem widget with a sample Task. - Task( - (b) => b - ..status = 'pending' - ..uuid = const Uuid().v1() - ..entry = DateTime.now() - ..description = 'foo', - ), - darkmode: true, // Specify dark mode. - ), - ), - ), - ); - }); -} diff --git a/test/widgets/profilefunctions/manageprofile_test.dart b/test/widgets/profilefunctions/manageprofile_test.dart deleted file mode 100644 index 938d6d16..00000000 --- a/test/widgets/profilefunctions/manageprofile_test.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:taskwarrior/widgets/profilefunctions/manageprofile.dart'; - -void main() { - // Define a test case for the "Manage Profile" widget. - testWidgets('test manage profile widget', (WidgetTester tester) async { - // Pump the widget tree and await its rendering. - await tester.pumpWidget( - MaterialApp( - // Create a Material app with the specified home widget. - home: Material( - child: ManageProfile( - () {}, - () {}, - () {}, - () {}, - () {}, - ), - ), - ), - ); - }); -} diff --git a/test/widgets/profilefunctions/selectprofile_test.dart b/test/widgets/profilefunctions/selectprofile_test.dart deleted file mode 100644 index 300ddf17..00000000 --- a/test/widgets/profilefunctions/selectprofile_test.dart +++ /dev/null @@ -1,52 +0,0 @@ -// Import necessary packages and libraries for testing. -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:taskwarrior/widgets/profilefunctions/selectprofile.dart'; - -void main() { - // Test case for testing the Profile Selection widget. - testWidgets('Profile selection widget test', (WidgetTester tester) async { - // Pump the widget tree and await its rendering. - await tester.pumpWidget( - MaterialApp( - // Create a Material app with the specified home widget. - home: Scaffold( - body: SelectProfile( - 'currentProfile', // Provide the current profile value. - const { - 'profile1': 'Profile 1', - 'profile2': 'Profile 2' - }, // Provide a map of profile data. - (String profile) {}, // Provide a callback function. - ), - ), - ), - ); - - // Expect to find the text 'Profile:' in the widget tree. - expect(find.text('Profile:'), findsOneWidget); - }); - - // Test case for testing the Manage Selected Profile widget. - testWidgets('Manage selected profile widget test', - (WidgetTester tester) async { - // Build the SelectProfileListTile widget. - await tester.pumpWidget( - MaterialApp( - // Create a Material app with the specified home widget. - home: Scaffold( - body: SelectProfileListTile( - '12h3fh3he-2b2b2ibdkb-sndh3dh34', // Provide a unique identifier. - 'uuid', // Provide a UUID. - () {}, // Provide a callback function. - 'Alias', // Provide an alias value. - ), - ), - ), - ); - - // Expect to find the text 'Alias' and 'uuid' in the widget tree. - expect(find.text('Alias'), findsOneWidget); - expect(find.text('uuid'), findsOneWidget); - }); -} diff --git a/test/widgets/project_filter_test.dart b/test/widgets/project_filter_test.dart deleted file mode 100644 index 5b9f945e..00000000 --- a/test/widgets/project_filter_test.dart +++ /dev/null @@ -1,50 +0,0 @@ -// Import necessary packages and libraries for testing. -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:taskwarrior/widgets/project_filter.dart'; -import 'package:taskwarrior/widgets/taskfunctions/projects.dart'; - -void main() { - // Define a test case for testing the "Projects Column" widget. - testWidgets('test project column widget', (WidgetTester tester) async { - // Pump the widget tree and await its rendering. - await tester.pumpWidget( - MaterialApp( - // Create a Material app with the specified home widget. - home: Material( - child: ProjectsColumn( - { - 'a': ProjectNode() - ..children = {'a.b'}, // Provide project hierarchy. - 'a.b': ProjectNode() - ..parent = 'a', // Provide sub-project hierarchy. - }, - 'foo', // Provide the currently selected project. - (_) {}, // Provide a callback function. - ), - ), - ), - ); - - // Simulate a tap on the 'project:foo' text. - await tester.tap(find.text('project:foo')); - await tester.pump(); - - // Expect to find the text 'a' and '0' in the widget tree. - expect(find.text('a'), findsOneWidget); - expect(find.text('0'), findsOneWidget); - - // Ensure 'a' is visible and wait for animations to settle. - await tester.ensureVisible(find.text('a')); - await tester.pumpAndSettle(); - - // Simulate a tap on the 'a' text. - await tester.tap(find.text('a')); - - // Expect to find the text 'a.b' in the widget tree. - expect(find.text('a.b'), findsOneWidget); - - // Simulate a tap on the 'a.b' text. - await tester.tap(find.text('a.b')); - }); -} diff --git a/test/widgets/tag_filter_test.dart b/test/widgets/tag_filter_test.dart deleted file mode 100644 index 3fbfa7af..00000000 --- a/test/widgets/tag_filter_test.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:taskwarrior/widgets/tag_filter.dart'; - -void main() { - // Define a test case for testing the "Tag Filter" widget. - testWidgets('test tag filter widget', (WidgetTester tester) async { - // Pump the widget tree and await its rendering. - await tester.pumpWidget( - MaterialApp( - // Create a Material app with the specified home widget. - home: Material( - child: TagFiltersWrap( - // Build the TagFilters widget with specific configuration. - TagFilters( - tagUnion: false, // Set tag union value. - toggleTagUnion: - () {}, // Provide a callback for toggling tag union. - tags: { - 'Tag': const TagFilterMetadata( - display: 'Tag', - selected: false), // Provide tag filter metadata. - }, - toggleTagFilter: - (_) {}, // Provide a callback for toggling tag filter. - ), - ), - ), - ), - ); - - // Expect to find the text 'AND' in the widget tree. - expect(find.text('AND'), findsOneWidget); - - // Simulate tapping on the 'AND' text. - await tester.tap(find.text('AND')); - await tester.pump(); - - // Expect to find the text 'Tag' in the widget tree. - expect(find.text('Tag'), findsOneWidget); - - // Simulate tapping on the 'Tag' text. - await tester.tap(find.text('Tag')); - }); -} diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 00000000..f53e96fa --- /dev/null +++ b/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + taskwarrior + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 00000000..3f675e88 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "taskwarrior", + "short_name": "taskwarrior", + "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" + } + ] +} diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/windows/.gitignore @@ -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/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 00000000..4a18b3d6 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(taskwarrior LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "taskwarrior") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +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() +# Define settings for the Profile build mode. +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. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +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 "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +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 "$") +# 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() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# 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) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..903f4899 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +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") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === 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" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..dbf82894 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,23 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + ConnectivityPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..c22844ad --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,27 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + connectivity_plus + file_selector_windows + permission_handler_windows + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +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 $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +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 the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 00000000..a0ba3176 --- /dev/null +++ b/windows/runner/Runner.rc @@ -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 +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#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.ccextractor" "\0" + VALUE "FileDescription", "taskwarrior" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "taskwarrior" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 com.ccextractor. All rights reserved." "\0" + VALUE "OriginalFilename", "taskwarrior.exe" "\0" + VALUE "ProductName", "taskwarrior" "\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 diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#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( + 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()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + 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 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); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#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_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 00000000..d06cb5a6 --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#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 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.Create(L"taskwarrior", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/windows/runner/resource.h @@ -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 diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/windows/runner/resources/app_icon.ico differ diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..a42ea768 --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 00000000..3a0b4651 --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +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 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::vector 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(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// 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, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// 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(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( + 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 registrar 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::Create(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(origin.x), + static_cast(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, + 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; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// 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(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(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(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; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + 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( + 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. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// 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 a win32 window with |title| that is positioned and sized 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 this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // 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 + // responds 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; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + 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_