From 51ff25e239158f9accb9315b63a7b83ca8170e24 Mon Sep 17 00:00:00 2001 From: rohansen856 Date: Mon, 13 Jan 2025 00:18:39 +0530 Subject: [PATCH 1/5] feat: added permission request screen --- .../views/onboarding_page_start_button.dart | 2 +- .../bindings/permission_binding.dart | 12 ++ .../controllers/permission_controller.dart | 57 +++++ .../permission/views/permission_view.dart | 197 ++++++++++++++++++ lib/app/routes/app_pages.dart | 7 + lib/app/routes/app_routes.dart | 2 + lib/main.dart | 4 - 7 files changed, 276 insertions(+), 5 deletions(-) create mode 100644 lib/app/modules/permission/bindings/permission_binding.dart create mode 100644 lib/app/modules/permission/controllers/permission_controller.dart create mode 100644 lib/app/modules/permission/views/permission_view.dart 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..d23838e5 --- /dev/null +++ b/lib/app/modules/permission/controllers/permission_controller.dart @@ -0,0 +1,57 @@ +import 'package:get/get.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class PermissionController extends GetxController { + final RxBool isStorageGranted = false.obs; + final RxBool isNotificationGranted = 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; + } catch (e) { + print('Error checking permissions: $e'); + } + } + + Future requestPermissions() async { + try { + isLoading.value = true; + + PermissionStatus storageStatus; + if (GetPlatform.isAndroid) { + storageStatus = await Permission.storage.request(); + } else { + storageStatus = await Permission.photos.request(); + } + isStorageGranted.value = storageStatus.isGranted; + + final notificationStatus = await Permission.notification.request(); + isNotificationGranted.value = notificationStatus.isGranted; + + if (isStorageGranted.value && isNotificationGranted.value) { + Get.offNamed('/home'); + } + } catch (e) { + print('Error requesting permissions: $e'); + } finally { + isLoading.value = false; + } + } + + void openSettings() async { + try { + await Get.offNamed('/home'); + } catch (e) { + print('Error opening settings: $e'); + } + } +} 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..25df6e44 --- /dev/null +++ b/lib/app/modules/permission/views/permission_view.dart @@ -0,0 +1,197 @@ +import 'dart:io'; + +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/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), + _buildPermissionSection( + context, + 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), + _buildPermissionSection( + context, + 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.openSettings(), + 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), + ], + ), + ), + ), + ), + ); + } + + Widget _buildPermissionSection( + BuildContext context, { + required IconData icon, + required String title, + required String description, + required bool isDarkMode, + }) { + 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/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", From c72e9377d3a285d0acf49ccc08a1c631315adca9 Mon Sep 17 00:00:00 2001 From: rohansen856 Date: Mon, 13 Jan 2025 00:33:03 +0530 Subject: [PATCH 2/5] fix: route update test updated --- test/routes/app_pages_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) => From 604c4e2c6c147d4dcf839225bf7c0c8ad22cfac2 Mon Sep 17 00:00:00 2001 From: rohansen856 Date: Mon, 13 Jan 2025 00:40:52 +0530 Subject: [PATCH 3/5] fix: fixed flutter analyze errors --- .../permission/controllers/permission_controller.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/app/modules/permission/controllers/permission_controller.dart b/lib/app/modules/permission/controllers/permission_controller.dart index d23838e5..dba9fe72 100644 --- a/lib/app/modules/permission/controllers/permission_controller.dart +++ b/lib/app/modules/permission/controllers/permission_controller.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -18,7 +19,7 @@ class PermissionController extends GetxController { isNotificationGranted.value = await Permission.notification.status.isGranted; } catch (e) { - print('Error checking permissions: $e'); + debugPrint('Error checking permissions: $e'); } } @@ -41,7 +42,7 @@ class PermissionController extends GetxController { Get.offNamed('/home'); } } catch (e) { - print('Error requesting permissions: $e'); + debugPrint('Error requesting permissions: $e'); } finally { isLoading.value = false; } @@ -51,7 +52,7 @@ class PermissionController extends GetxController { try { await Get.offNamed('/home'); } catch (e) { - print('Error opening settings: $e'); + debugPrint('Error opening home screen: $e'); } } } From 97aad86281c6fc6ba8914f1780a397dc61feaf00 Mon Sep 17 00:00:00 2001 From: rohansen856 Date: Mon, 13 Jan 2025 21:01:34 +0530 Subject: [PATCH 4/5] updated permission request functionality --- .../controllers/permission_controller.dart | 24 ++++++++----------- .../permission/views/permission_view.dart | 2 +- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/lib/app/modules/permission/controllers/permission_controller.dart b/lib/app/modules/permission/controllers/permission_controller.dart index dba9fe72..229656c1 100644 --- a/lib/app/modules/permission/controllers/permission_controller.dart +++ b/lib/app/modules/permission/controllers/permission_controller.dart @@ -1,10 +1,12 @@ 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 @@ -18,6 +20,8 @@ class PermissionController extends GetxController { 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'); } @@ -27,20 +31,12 @@ class PermissionController extends GetxController { try { isLoading.value = true; - PermissionStatus storageStatus; - if (GetPlatform.isAndroid) { - storageStatus = await Permission.storage.request(); - } else { - storageStatus = await Permission.photos.request(); - } - isStorageGranted.value = storageStatus.isGranted; - - final notificationStatus = await Permission.notification.request(); - isNotificationGranted.value = notificationStatus.isGranted; - - if (isStorageGranted.value && isNotificationGranted.value) { - Get.offNamed('/home'); + if (!isStorageGranted.value && + !isNotificationGranted.value && + !isExteternalStorageGranted.value) { + await PermissionsManager.requestAllPermissions(); } + Get.offNamed('/home'); } catch (e) { debugPrint('Error requesting permissions: $e'); } finally { @@ -48,7 +44,7 @@ class PermissionController extends GetxController { } } - void openSettings() async { + void gotoHome() async { try { await Get.offNamed('/home'); } catch (e) { diff --git a/lib/app/modules/permission/views/permission_view.dart b/lib/app/modules/permission/views/permission_view.dart index 25df6e44..08737c5b 100644 --- a/lib/app/modules/permission/views/permission_view.dart +++ b/lib/app/modules/permission/views/permission_view.dart @@ -117,7 +117,7 @@ class PermissionView extends GetView { )), const SizedBox(height: 16), TextButton( - onPressed: () => controller.openSettings(), + onPressed: () => controller.gotoHome(), style: ButtonStyle( backgroundColor: WidgetStateProperty.all(TaskWarriorColors.grey), From 5434792ae2ea978034754cfe0b94b4d61080be57 Mon Sep 17 00:00:00 2001 From: rohansen856 Date: Mon, 13 Jan 2025 21:13:34 +0530 Subject: [PATCH 5/5] converted functional component to StatelessWidget --- .../permission/views/permission_section.dart | 66 +++++++++++++++++++ .../permission/views/permission_view.dart | 61 +---------------- 2 files changed, 69 insertions(+), 58 deletions(-) create mode 100644 lib/app/modules/permission/views/permission_section.dart 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 index 08737c5b..70537e40 100644 --- a/lib/app/modules/permission/views/permission_view.dart +++ b/lib/app/modules/permission/views/permission_view.dart @@ -2,6 +2,7 @@ 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'; @@ -51,8 +52,7 @@ class PermissionView extends GetView { : TaskWarriorColors.white, ), const SizedBox(height: 32), - _buildPermissionSection( - context, + PermissionSection( icon: Icons.folder_outlined, title: 'Storage Permission', description: @@ -62,8 +62,7 @@ class PermissionView extends GetView { isDarkMode: isDarkMode, ), const SizedBox(height: 24), - _buildPermissionSection( - context, + PermissionSection( icon: Icons.notifications_outlined, title: 'Notification Permission', description: @@ -140,58 +139,4 @@ class PermissionView extends GetView { ), ); } - - Widget _buildPermissionSection( - BuildContext context, { - required IconData icon, - required String title, - required String description, - required bool isDarkMode, - }) { - 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, - ), - ), - ], - ), - ); - } }