diff --git a/lib/app/modules/onboarding/views/onboarding_page_start_button.dart b/lib/app/modules/onboarding/views/onboarding_page_start_button.dart index b7b2cbdc..5933e3bb 100644 --- a/lib/app/modules/onboarding/views/onboarding_page_start_button.dart +++ b/lib/app/modules/onboarding/views/onboarding_page_start_button.dart @@ -16,7 +16,7 @@ class OnboardingPageStartButton extends StatelessWidget { child: ElevatedButton( onPressed: () { controller.markOnboardingAsCompleted(); - Get.offNamed(Routes.HOME); + Get.offNamed(Routes.PERMISSION); }, style: ElevatedButton.styleFrom( backgroundColor: TaskWarriorColors.black, diff --git a/lib/app/modules/permission/bindings/permission_binding.dart b/lib/app/modules/permission/bindings/permission_binding.dart new file mode 100644 index 00000000..f1aaa7da --- /dev/null +++ b/lib/app/modules/permission/bindings/permission_binding.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; + +import '../controllers/permission_controller.dart'; + +class PermissionBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => PermissionController(), + ); + } +} diff --git a/lib/app/modules/permission/controllers/permission_controller.dart b/lib/app/modules/permission/controllers/permission_controller.dart new file mode 100644 index 00000000..229656c1 --- /dev/null +++ b/lib/app/modules/permission/controllers/permission_controller.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:taskwarrior/app/utils/permissions/permissions_manager.dart'; + +class PermissionController extends GetxController { + final RxBool isStorageGranted = false.obs; + final RxBool isNotificationGranted = false.obs; + final RxBool isExteternalStorageGranted = false.obs; + final RxBool isLoading = false.obs; + + @override + void onInit() { + super.onInit(); + checkPermissions(); + } + + Future checkPermissions() async { + try { + isStorageGranted.value = await Permission.storage.status.isGranted; + isNotificationGranted.value = + await Permission.notification.status.isGranted; + isExteternalStorageGranted.value = + await Permission.manageExternalStorage.status.isGranted; + } catch (e) { + debugPrint('Error checking permissions: $e'); + } + } + + Future requestPermissions() async { + try { + isLoading.value = true; + + if (!isStorageGranted.value && + !isNotificationGranted.value && + !isExteternalStorageGranted.value) { + await PermissionsManager.requestAllPermissions(); + } + Get.offNamed('/home'); + } catch (e) { + debugPrint('Error requesting permissions: $e'); + } finally { + isLoading.value = false; + } + } + + void gotoHome() async { + try { + await Get.offNamed('/home'); + } catch (e) { + debugPrint('Error opening home screen: $e'); + } + } +} diff --git a/lib/app/modules/permission/views/permission_section.dart b/lib/app/modules/permission/views/permission_section.dart new file mode 100644 index 00000000..0afa409f --- /dev/null +++ b/lib/app/modules/permission/views/permission_section.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; + +class PermissionSection extends StatelessWidget { + final IconData icon; + final String title; + final String description; + final bool isDarkMode; + + const PermissionSection({ + super.key, + required this.icon, + required this.title, + required this.description, + required this.isDarkMode, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border.all( + color: isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.borderColor, + ), + borderRadius: BorderRadius.circular(12), + color: isDarkMode + ? TaskWarriorColors.kdialogBackGroundColor + : TaskWarriorColors.kLightDialogBackGroundColor, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, color: TaskWarriorColors.black), + const SizedBox(width: 12), + Expanded( + child: Text( + title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + description, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: isDarkMode + ? TaskWarriorColors.ksecondaryTextColor + : TaskWarriorColors.kLightSecondaryTextColor, + ), + ), + ], + ), + ); + } +} diff --git a/lib/app/modules/permission/views/permission_view.dart b/lib/app/modules/permission/views/permission_view.dart new file mode 100644 index 00000000..70537e40 --- /dev/null +++ b/lib/app/modules/permission/views/permission_view.dart @@ -0,0 +1,142 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/permission/views/permission_section.dart'; +import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import '../controllers/permission_controller.dart'; + +class PermissionView extends GetView { + const PermissionView({super.key}); + + @override + Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) { + WidgetsBinding.instance.addPostFrameCallback((_) { + Get.offAllNamed('/home'); + }); + return const SizedBox.shrink(); + } + + return Scaffold( + backgroundColor: isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + body: SafeArea( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 24), + Text( + 'Why We Need Your Permission', + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + color: isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 32), + Icon( + Icons.security, + size: 64, + color: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + ), + const SizedBox(height: 32), + PermissionSection( + icon: Icons.folder_outlined, + title: 'Storage Permission', + description: + 'We use storage access to save your tasks, preferences, ' + 'and app data securely on your device. This ensures that you can ' + 'pick up where you left off seamlessly, even offline.', + isDarkMode: isDarkMode, + ), + const SizedBox(height: 24), + PermissionSection( + icon: Icons.notifications_outlined, + title: 'Notification Permission', + description: + 'Notifications keep you updated with important reminders ' + 'and updates, ensuring you stay on top of your tasks effortlessly.', + isDarkMode: isDarkMode, + ), + const SizedBox(height: 24), + Text( + 'Your privacy is our top priority. We never access or share your ' + 'personal files or data without your consent.', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 48), + Obx(() => ElevatedButton( + onPressed: controller.isLoading.value + ? null + : controller.requestPermissions, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.all(16), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + foregroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: controller.isLoading.value + ? CircularProgressIndicator( + color: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + ) + : Text( + 'Grant Permissions', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + fontSize: 16, + ), + ), + )), + const SizedBox(height: 16), + TextButton( + onPressed: () => controller.gotoHome(), + style: ButtonStyle( + backgroundColor: + WidgetStateProperty.all(TaskWarriorColors.grey), + ), + child: Text( + 'You can manage your permissions anytime later in Settings', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.white, + ), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 24), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index f6925604..0cc14c1b 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -13,6 +13,8 @@ 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/permission/bindings/permission_binding.dart'; +import '../modules/permission/views/permission_view.dart'; import '../modules/profile/bindings/profile_binding.dart'; import '../modules/profile/views/profile_view.dart'; import '../modules/reports/bindings/reports_binding.dart'; @@ -75,5 +77,10 @@ class AppPages { page: () => const SettingsView(), binding: SettingsBinding(), ), + GetPage( + name: _Paths.PERMISSION, + page: () => const PermissionView(), + binding: PermissionBinding(), + ), ]; } diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index 5cf53cc3..b04f381a 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -14,6 +14,7 @@ abstract class Routes { static const ABOUT = _Paths.ABOUT; static const REPORTS = _Paths.REPORTS; static const SETTINGS = _Paths.SETTINGS; + static const PERMISSION = _Paths.PERMISSION; } abstract class _Paths { @@ -27,4 +28,5 @@ abstract class _Paths { static const ABOUT = '/about'; static const REPORTS = '/reports'; static const SETTINGS = '/settings'; + static const PERMISSION = '/permission'; } diff --git a/lib/main.dart b/lib/main.dart index a88ff580..b9faee90 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,16 +1,12 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; -import 'package:taskwarrior/app/utils/permissions/permissions_manager.dart'; import 'app/routes/app_pages.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await AppSettings.init(); - await PermissionsManager.requestAllPermissions(); - - runApp( GetMaterialApp( title: "Application", diff --git a/test/routes/app_pages_test.dart b/test/routes/app_pages_test.dart index 95df3f25..58d8fd91 100644 --- a/test/routes/app_pages_test.dart +++ b/test/routes/app_pages_test.dart @@ -30,7 +30,7 @@ void main() { test('All routes should be defined correctly', () { final routes = AppPages.routes; - expect(routes.length, 9); + expect(routes.length, 10); expect( routes.any((route) =>