From 68260b7b253cfac9e52131fb7a3c10fd45cf6a24 Mon Sep 17 00:00:00 2001 From: David <87503695+daoxve@users.noreply.github.com> Date: Thu, 8 Dec 2022 06:00:05 +0100 Subject: [PATCH 1/4] fix: Build errors --- ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Release.xcconfig | 1 + ios/Podfile | 74 +++++++++++++++ ios/Podfile.lock | 95 +++++++++++++++++++ ios/Runner.xcodeproj/project.pbxproj | 68 +++++++++++++ .../contents.xcworkspacedata | 3 + pubspec.lock | 8 +- pubspec.yaml | 4 + 8 files changed, 251 insertions(+), 3 deletions(-) create mode 100644 ios/Podfile create mode 100644 ios/Podfile.lock diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..9c1d5bf --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,74 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +def flutter_install_plugin_pods(application_path = nil, relative_symlink_dir, platform) + # defined_in_file is set by CocoaPods and is a Pathname to the Podfile. + application_path ||= File.dirname(defined_in_file.realpath) if self.respond_to?(:defined_in_file) + raise 'Could not find application path' unless application_path + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + + symlink_dir = File.expand_path(relative_symlink_dir, application_path) + system('rm', '-rf', symlink_dir) # Avoid the complication of dependencies like FileUtils. + + symlink_plugins_dir = File.expand_path('plugins', symlink_dir) + system('mkdir', '-p', symlink_plugins_dir) + + plugins_file = File.join(application_path, '..', '.flutter-plugins-dependencies') + plugin_pods = flutter_parse_plugins_file(plugins_file, platform) + plugin_pods.each do |plugin_hash| + plugin_name = plugin_hash['name'] + plugin_path = plugin_hash['path'] + if (plugin_name && plugin_path) + symlink = File.join(symlink_plugins_dir, plugin_name) + File.symlink(plugin_path, symlink) + + if plugin_name == 'flutter_ffmpeg' + pod 'flutter_ffmpeg/min-lts', :path => File.join(relative_symlink_dir, 'plugins', plugin_name, platform) + else + pod plugin_name, :path => File.join(relative_symlink_dir, 'plugins', plugin_name, platform) + end + end + end +end + + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..60acce5 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,95 @@ +PODS: + - camera (0.0.1): + - Flutter + - Flutter (1.0.0) + - flutter_ffmpeg/min-lts (0.4.2): + - Flutter + - mobile-ffmpeg-min (= 4.4.LTS) + - flutter_local_notifications (0.0.1): + - Flutter + - mobile-ffmpeg-min (4.4.LTS) + - native_device_orientation (0.0.1): + - Flutter + - open_file (0.0.1): + - Flutter + - path_provider_ios (0.0.1): + - Flutter + - permission_handler_apple (9.0.4): + - Flutter + - share (0.0.1): + - Flutter + - shared_preferences_ios (0.0.1): + - Flutter + - tapioca (0.0.1): + - Flutter + - url_launcher_ios (0.0.1): + - Flutter + - video_player_avfoundation (0.0.1): + - Flutter + +DEPENDENCIES: + - camera (from `.symlinks/plugins/camera/ios`) + - Flutter (from `Flutter`) + - flutter_ffmpeg/min-lts (from `.symlinks/plugins/flutter_ffmpeg/ios`) + - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) + - native_device_orientation (from `.symlinks/plugins/native_device_orientation/ios`) + - open_file (from `.symlinks/plugins/open_file/ios`) + - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - share (from `.symlinks/plugins/share/ios`) + - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) + - tapioca (from `.symlinks/plugins/tapioca/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`) + +SPEC REPOS: + trunk: + - mobile-ffmpeg-min + +EXTERNAL SOURCES: + camera: + :path: ".symlinks/plugins/camera/ios" + Flutter: + :path: Flutter + flutter_ffmpeg: + :path: ".symlinks/plugins/flutter_ffmpeg/ios" + flutter_local_notifications: + :path: ".symlinks/plugins/flutter_local_notifications/ios" + native_device_orientation: + :path: ".symlinks/plugins/native_device_orientation/ios" + open_file: + :path: ".symlinks/plugins/open_file/ios" + path_provider_ios: + :path: ".symlinks/plugins/path_provider_ios/ios" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" + share: + :path: ".symlinks/plugins/share/ios" + shared_preferences_ios: + :path: ".symlinks/plugins/shared_preferences_ios/ios" + tapioca: + :path: ".symlinks/plugins/tapioca/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + video_player_avfoundation: + :path: ".symlinks/plugins/video_player_avfoundation/ios" + +SPEC CHECKSUMS: + camera: a0ca5080336f7af47b88436e5e26da3dee5568f0 + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + flutter_ffmpeg: 3ec3912c74649851e6c3b1885511eef78142ac4f + flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 + mobile-ffmpeg-min: dd2fd38da2788ccb7be5720fc308cb69160820e7 + native_device_orientation: 3b4cfc9565a7b879cc4fde282b3e27745e852d0d + open_file: 02eb5cb6b21264bd3a696876f5afbfb7ca4f4b7d + path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 + permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce + share: 0b2c3e82132f5888bccca3351c504d0003b3b410 + shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad + tapioca: 59e9fc89671d49e2cefe6cd59c3efe678e589e89 + url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de + video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff + +PODFILE CHECKSUM: 3460e53762ceea3c3b40d0e687654910ee3996d4 + +COCOAPODS: 1.11.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 5ebdcb5..d627119 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + E23B2C7D9C5BDEFE4578B653 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D698C0C638BF109A9222E0 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -31,10 +32,13 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 30287852AE1EC6414B5838DB /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 54DCFD44EA73E1FBA61B8993 /* 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 = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8EE8D3BA54297AF6D51FF494 /* 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 = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -42,6 +46,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A7D698C0C638BF109A9222E0 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,12 +54,24 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E23B2C7D9C5BDEFE4578B653 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 16FC201379E3377E6983DA82 /* Pods */ = { + isa = PBXGroup; + children = ( + 30287852AE1EC6414B5838DB /* Pods-Runner.debug.xcconfig */, + 54DCFD44EA73E1FBA61B8993 /* Pods-Runner.release.xcconfig */, + 8EE8D3BA54297AF6D51FF494 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +89,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 16FC201379E3377E6983DA82 /* Pods */, + 9BA2E7821CEBAB0541C4CDDF /* Frameworks */, ); sourceTree = ""; }; @@ -98,6 +117,14 @@ path = Runner; sourceTree = ""; }; + 9BA2E7821CEBAB0541C4CDDF /* Frameworks */ = { + isa = PBXGroup; + children = ( + A7D698C0C638BF109A9222E0 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -105,12 +132,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 3A21B97CBF1D07743468E737 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 0C416C5F01E20C0EE19E5AA2 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -169,6 +198,45 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 0C416C5F01E20C0EE19E5AA2 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3A21B97CBF1D07743468E737 /* [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; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/pubspec.lock b/pubspec.lock index 15881c7..4600a08 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -558,9 +558,11 @@ packages: tapioca: dependency: "direct main" description: - name: tapioca - url: "https://pub.dartlang.org" - source: hosted + path: "." + ref: android-dependency-patch + resolved-ref: "6e06265527918355cff13ca5415486f819fbeb7a" + url: "https://github.com/daoxve/tapioca.git" + source: git version: "1.0.6+1" term_glyph: dependency: transitive diff --git a/pubspec.yaml b/pubspec.yaml index 700e722..9895951 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,7 +22,11 @@ dependencies: shared_preferences: flutter_ffmpeg: permission_handler: + # TODO: remove this in the future & reference the official package when higher kotlin versions are supported tapioca: + git: + url: https://github.com/daoxve/tapioca.git + ref: android-dependency-patch # branch name salomon_bottom_bar: introduction_screen: avatar_glow: From 50f516d246d231ae736ca725423085a5bf818bbb Mon Sep 17 00:00:00 2001 From: David <87503695+daoxve@users.noreply.github.com> Date: Sat, 10 Dec 2022 17:34:16 +0100 Subject: [PATCH 2/4] Setup scheduled notifications --- lib/lang/de.dart | 3 + lib/lang/en.dart | 3 + lib/lang/es.dart | 3 + lib/lang/fr.dart | 3 + lib/lang/id.dart | 3 + lib/lang/pt.dart | 3 + lib/lang/zh.dart | 3 + .../home/notification/notification_page.dart | 33 +++ .../widgets/switch_notifications.dart | 252 ++++++++++++++++++ lib/pages/home/settings/settings_page.dart | 4 +- .../widgets/notifications_button.dart | 32 +++ .../widgets/switch_notifications.dart | 142 ---------- lib/routes/app_pages.dart | 2 + lib/routes/app_routes.dart | 1 + lib/utils/theme.dart | 15 ++ 15 files changed, 358 insertions(+), 144 deletions(-) create mode 100644 lib/pages/home/notification/notification_page.dart create mode 100644 lib/pages/home/notification/widgets/switch_notifications.dart create mode 100644 lib/pages/home/settings/widgets/notifications_button.dart delete mode 100644 lib/pages/home/settings/widgets/switch_notifications.dart diff --git a/lib/lang/de.dart b/lib/lang/de.dart index 87363fb..7a11af7 100644 --- a/lib/lang/de.dart +++ b/lib/lang/de.dart @@ -64,6 +64,9 @@ const Map de = { 'foundVideos': 'Videos gefunden!', 'foundVideo': 'Video gefunden!', 'notifications': 'Benachrichtigungen', + 'enableNotifications': 'Benachrichtigungen aktivieren', + 'scheduleTime': 'Planmäßige Zeit', + 'test': 'Prüfen', 'notificationTitle': 'Hallo!', 'notificationBody': 'Vergiss nicht 1 Sekunde Deines Tages aufzuzeichnen 👀', 'recordingSettings': 'Aufnahmeeinstellungen', diff --git a/lib/lang/en.dart b/lib/lang/en.dart index 39fd919..6275c77 100644 --- a/lib/lang/en.dart +++ b/lib/lang/en.dart @@ -64,6 +64,9 @@ const Map en = { 'foundVideos': 'videos found!', 'foundVideo': 'video found!', 'notifications': 'Notifications', + 'enableNotifications': 'Enable Notifications', + 'scheduleTime': 'Schedule Time', + 'test': 'Test', 'notificationTitle': 'Heyy!', 'notificationBody': 'Do not forget to record 1 second of your day 👀', 'recordingSettings': 'Recording Settings', diff --git a/lib/lang/es.dart b/lib/lang/es.dart index 4e58ce2..f78e855 100644 --- a/lib/lang/es.dart +++ b/lib/lang/es.dart @@ -63,6 +63,9 @@ const Map es = { 'foundVideos': 'videos encontrados!', 'foundVideo': 'video encontrado!', 'notifications': 'Notificaciones', + 'enableNotifications': 'Permitir notificaciones', + 'scheduleTime': 'Tiempo programado', + 'test': 'Prueba', 'notificationTitle': '¡Heyy!', 'notificationBody': 'No olvides grabar 1 segundo de tu día 👀', 'recordingSettings': 'Ajustes de Grabación', diff --git a/lib/lang/fr.dart b/lib/lang/fr.dart index 1bae4e5..640b875 100644 --- a/lib/lang/fr.dart +++ b/lib/lang/fr.dart @@ -64,6 +64,9 @@ const Map fr = { 'foundVideos': 'vidéos trouvées !', 'foundVideo': 'vidéo trouvée !', 'notifications': 'Notifications', + 'enableNotifications': 'Activer les notifications', + 'scheduleTime': 'Horaire', + 'test': 'Test', 'notificationTitle': 'Hé !', 'notificationBody': "N'oubliez pas d'enregistrer une seconde de votre journée. 👀", diff --git a/lib/lang/id.dart b/lib/lang/id.dart index bdf0fbf..59cb5be 100644 --- a/lib/lang/id.dart +++ b/lib/lang/id.dart @@ -64,6 +64,9 @@ const Map id = { 'foundVideos': 'video ditemukan!', 'foundVideo': 'video ditemukan!', 'notifications': 'Notifikasi', + 'enableNotifications': 'Aktifkan pemberitahuan', + 'scheduleTime': 'Jadwal waktu', + 'test': 'Uji', 'notificationTitle': 'Hai!', 'notificationBody': 'Jangan lupa untuk merekam 1 detik hari ini 👀', 'recordingSettings': 'Pengaturan Perekaman', diff --git a/lib/lang/pt.dart b/lib/lang/pt.dart index b15ec29..3bf520f 100644 --- a/lib/lang/pt.dart +++ b/lib/lang/pt.dart @@ -63,6 +63,9 @@ const Map pt = { 'foundVideos': 'vídeos encontrados!', 'foundVideo': 'vídeo encontrado!', 'notifications': 'Notificações', + 'enableNotifications': 'Ativar notificações', + 'scheduleTime': 'Hora agendada', + 'test': 'Teste', 'notificationTitle': 'Psiuu!', 'notificationBody': 'Não se esqueça de gravar 1 segundo do seu dia 👀', 'recordingSettings': 'Configurações de Gravação', diff --git a/lib/lang/zh.dart b/lib/lang/zh.dart index 5991e3a..8ff462b 100644 --- a/lib/lang/zh.dart +++ b/lib/lang/zh.dart @@ -57,6 +57,9 @@ const Map zh = { 'foundVideos': '寻找视频集!', 'foundVideo': '寻找视频!', 'notifications': '通知', + 'enableNotifications': '启用通知', + 'scheduleTime': '时间安排时间', + 'test': '测试', 'notificationTitle': '嘿!', 'notificationBody': '不要忘记记录一天中的 1 秒👀', 'recordingSettings': '录制设置', diff --git a/lib/pages/home/notification/notification_page.dart b/lib/pages/home/notification/notification_page.dart new file mode 100644 index 0000000..3eb8b87 --- /dev/null +++ b/lib/pages/home/notification/notification_page.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'widgets/switch_notifications.dart'; + +class NotificationPage extends StatelessWidget { + const NotificationPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + 'notifications'.tr, + style: TextStyle( + fontFamily: 'Magic', + fontSize: MediaQuery.of(context).size.width * 0.05, + ), + ), + ), + body: Center( + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.9, + height: MediaQuery.of(context).size.height * 0.8, + child: Column( + children: [ + SwitchNotificationsComponent(), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/home/notification/widgets/switch_notifications.dart b/lib/pages/home/notification/widgets/switch_notifications.dart new file mode 100644 index 0000000..6246384 --- /dev/null +++ b/lib/pages/home/notification/widgets/switch_notifications.dart @@ -0,0 +1,252 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:get/get.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:timezone/data/latest.dart' as tz; +import 'package:timezone/timezone.dart' as tz; + +import '../../../../routes/app_pages.dart'; +import '../../../../utils/constants.dart'; +import '../../../../utils/notification_service.dart'; +import '../../../../utils/shared_preferences_util.dart'; +import '../../../../utils/theme.dart'; +import '../../../../utils/utils.dart'; + +class SwitchNotificationsComponent extends StatefulWidget { + @override + _SwitchNotificationsComponentState createState() => _SwitchNotificationsComponentState(); +} + +class _SwitchNotificationsComponentState extends State { + final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + + final int notificationId = 1; + + TimeOfDay scheduledTimeOfDay = const TimeOfDay(hour: 20, minute: 00); + + @override + void initState() { + super.initState(); + + // Sets the default values for scheduled time + getScheduledTime(); + + /// Initializing notification settings + tz.initializeTimeZones(); + + const AndroidInitializationSettings androidInitializationSettings = + AndroidInitializationSettings('@mipmap/ic_launcher'); + + const DarwinInitializationSettings iosInitializationSettings = + DarwinInitializationSettings(); + + const InitializationSettings initializationSettings = InitializationSettings( + android: androidInitializationSettings, + iOS: iosInitializationSettings, + ); + + flutterLocalNotificationsPlugin.initialize(initializationSettings); + } + + @override + void dispose() { + super.dispose(); + } + + final platformNotificationDetails = const NotificationDetails( + android: AndroidNotificationDetails( + 'channel id', + 'channel name', + channelDescription: 'channel description', + ), + ); + + // Checks for the scheduled time and sets it to a value in shared prefs + void getScheduledTime() { + final int hour = SharedPrefsUtil.getInt('scheduledTimeHour') ?? 20; + final int minute = SharedPrefsUtil.getInt('scheduledTimeMinute') ?? 00; + scheduledTimeOfDay = TimeOfDay(hour: hour, minute: minute); + } + + Future scheduleNotification() async { + /// Get current hour + final hour = DateTime.now().hour; + + /// Since there isn't a way to choose the best time to display, it will be shown around 18pm + const int defaultNotificationHour = 18; + + /// Difference between current hour and 12am to calculate scheduling + int difference = 0; + + /// Final hour distance that notification will be shown + int notificationHour = 0; + + if (hour >= defaultNotificationHour) { + difference = hour - defaultNotificationHour; + notificationHour = 24 - difference; + } else { + difference = defaultNotificationHour - hour; + notificationHour = difference; + } + + // print('notification will be shown in: $notificationHour hours from now'); + + /// Schedule notification + flutterLocalNotificationsPlugin.zonedSchedule( + notificationId, + 'notificationTitle'.tr, + 'notificationBody'.tr, + tz.TZDateTime.now(tz.local).add(Duration(hours: notificationHour)), + // tz.TZDateTime.now(tz.local).add(const Duration(seconds: 3)), + platformNotificationDetails, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, + androidAllowWhileIdle: true, + // Allow notification to be shown daily + matchDateTimeComponents: DateTimeComponents.time, + ); + } + + Future showTestNotification() async { + await flutterLocalNotificationsPlugin.show( + notificationId, + 'test'.tr, + 'test'.tr, + platformNotificationDetails, + ); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + GestureDetector( + onTap: () => Get.toNamed(Routes.NOTIFICATION), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 15.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'enableNotifications'.tr, + style: TextStyle( + fontSize: MediaQuery.of(context).size.width * 0.045, + ), + ), + ValueBuilder( + initialValue: NotificationService().isNotificationActivated(), + builder: (isChecked, updateFn) => Switch( + value: isChecked!, + onChanged: (value) async { + /// Save notification on SharedPrefs + NotificationService().switchNotification(); + + /// Update switch value + updateFn(NotificationService().isNotificationActivated()); + + if (NotificationService().isNotificationActivated()) { + /// Schedule notification if switch in ON + await Utils.requestPermission(Permission.notification); + await scheduleNotification(); + } else { + /// Cancel notification if switch is OFF + flutterLocalNotificationsPlugin.cancelAll(); + } + + // Set state should be called for the test widget to revalidate the notification toggle + setState(() {}); + }, + activeTrackColor: AppColors.mainColor.withOpacity(0.4), + activeColor: AppColors.mainColor, + ), + ), + ], + ), + ), + ), + const Divider(), + if (NotificationService().isNotificationActivated()) ...{ + InkWell( + onTap: showTestNotification, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 15.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'test'.tr, + style: TextStyle( + fontSize: MediaQuery.of(context).size.width * 0.045, + ), + ), + IconButton( + onPressed: showTestNotification, + splashRadius: 24, + icon: const Icon( + Icons.play_arrow, + color: Colors.green, + ), + ), + ], + ), + ), + ), + const Divider(), + }, + InkWell( + onTap: () async { + final TimeOfDay? newTimeOfDay = await showTimePicker( + context: context, + initialTime: scheduledTimeOfDay, + builder: (context, child) => Theme( + data: Theme.of(context).copyWith( + colorScheme: ThemeService().isDarkTheme() + ? const ColorScheme.dark( + primary: AppColors.mainColor, + onSurface: AppColors.light, + ) + : const ColorScheme.light( + primary: AppColors.dark, + onSurface: AppColors.dark, + ), + ), + child: child!, + ), + ); + + /// If the 'Cancel' button is pressed or the user quits without setting a time, `newTime` becomes null + if (newTimeOfDay == null) return; + + setState(() { + scheduledTimeOfDay = newTimeOfDay; + SharedPrefsUtil.putInt('scheduledTimeHour', newTimeOfDay.hour); + SharedPrefsUtil.putInt('scheduledTimeMinute', newTimeOfDay.minute); + }); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'scheduleTime'.tr, + style: TextStyle( + fontSize: MediaQuery.of(context).size.width * 0.045, + ), + ), + Text( + '${scheduledTimeOfDay.format(context)}', + style: TextStyle( + fontSize: MediaQuery.of(context).size.width * 0.045, + ), + ), + ], + ), + ), + ), + const Divider(), + ], + ); + } +} diff --git a/lib/pages/home/settings/settings_page.dart b/lib/pages/home/settings/settings_page.dart index b1f8235..8262970 100644 --- a/lib/pages/home/settings/settings_page.dart +++ b/lib/pages/home/settings/settings_page.dart @@ -5,7 +5,7 @@ import 'widgets/backup_tutorial.dart'; import 'widgets/contact_button.dart'; import 'widgets/github_button.dart'; import 'widgets/language_chooser.dart'; -import 'widgets/switch_notifications.dart'; +import 'widgets/notifications_button.dart'; import 'widgets/switch_theme.dart'; class SettingPage extends StatelessWidget { @@ -17,7 +17,7 @@ class SettingPage extends StatelessWidget { child: Column( children: [ SwitchThemeComponent(), - SwitchNotificationsComponent(), + NotificationsButton(), LanguageChooser(), BackupTutorial(), GithubButton(), diff --git a/lib/pages/home/settings/widgets/notifications_button.dart b/lib/pages/home/settings/widgets/notifications_button.dart new file mode 100644 index 0000000..a155cbc --- /dev/null +++ b/lib/pages/home/settings/widgets/notifications_button.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../../../routes/app_pages.dart'; + +class NotificationsButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + GestureDetector( + onTap: () => Get.toNamed(Routes.NOTIFICATION), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'notifications'.tr, + style: TextStyle( + fontSize: MediaQuery.of(context).size.width * 0.045, + ), + ), + ], + ), + ), + ), + const Divider(), + ], + ); + } +} diff --git a/lib/pages/home/settings/widgets/switch_notifications.dart b/lib/pages/home/settings/widgets/switch_notifications.dart deleted file mode 100644 index 6c92574..0000000 --- a/lib/pages/home/settings/widgets/switch_notifications.dart +++ /dev/null @@ -1,142 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'package:get/get.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:timezone/data/latest.dart' as tz; -import 'package:timezone/timezone.dart' as tz; - -import '../../../../utils/constants.dart'; -import '../../../../utils/notification_service.dart'; -import '../../../../utils/utils.dart'; - -class SwitchNotificationsComponent extends StatefulWidget { - @override - _SwitchNotificationsComponentState createState() => - _SwitchNotificationsComponentState(); -} - -class _SwitchNotificationsComponentState - extends State { - final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); - - final int notificationId = 1; - - @override - void initState() { - super.initState(); - - /// Initializing notification settings - tz.initializeTimeZones(); - - const AndroidInitializationSettings androidInitializationSettings = - AndroidInitializationSettings('@mipmap/ic_launcher'); - - const DarwinInitializationSettings iosInitializationSettings = - DarwinInitializationSettings(); - - const InitializationSettings initializationSettings = - InitializationSettings( - android: androidInitializationSettings, - iOS: iosInitializationSettings, - ); - - flutterLocalNotificationsPlugin.initialize(initializationSettings); - } - - @override - void dispose() { - super.dispose(); - } - - Future scheduleNotification() async { - /// Get current hour - final hour = DateTime.now().hour; - - /// Since there isn't a way to choose the best time to display, it will be shown around 18pm - const int defaultNotificationHour = 18; - - /// Difference between current hour and 12am to calculate scheduling - int difference = 0; - - /// Final hour distance that notification will be shown - int notificationHour = 0; - - if (hour >= defaultNotificationHour) { - difference = hour - defaultNotificationHour; - notificationHour = 24 - difference; - } else { - difference = defaultNotificationHour - hour; - notificationHour = difference; - } - - // print('notification will be shown in: $notificationHour hours from now'); - - /// Schedule notification - flutterLocalNotificationsPlugin.zonedSchedule( - notificationId, - 'notificationTitle'.tr, - 'notificationBody'.tr, - tz.TZDateTime.now(tz.local).add(Duration(hours: notificationHour)), - const NotificationDetails( - android: AndroidNotificationDetails( - 'channel id', - 'channel name', - channelDescription: 'channel description', - ), - ), - uiLocalNotificationDateInterpretation: - UILocalNotificationDateInterpretation.absoluteTime, - androidAllowWhileIdle: true, - // Allow notification to be shown daily - matchDateTimeComponents: DateTimeComponents.time, - ); - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 15.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'notifications'.tr, - style: TextStyle( - fontSize: MediaQuery.of(context).size.width * 0.045, - ), - ), - ValueBuilder( - initialValue: NotificationService().isNotificationActivated(), - builder: (isChecked, updateFn) => Switch( - value: isChecked!, - onChanged: (value) async { - /// Save notification on SharedPrefs - NotificationService().switchNotification(); - - /// Update switch value - updateFn(NotificationService().isNotificationActivated()); - - if (NotificationService().isNotificationActivated()) { - /// Schedule notification if switch in ON - await Utils.requestPermission(Permission.notification); - await scheduleNotification(); - } else { - /// Cancel notification if switch is OFF - flutterLocalNotificationsPlugin.cancelAll(); - } - }, - activeTrackColor: AppColors.mainColor.withOpacity(0.4), - activeColor: AppColors.mainColor, - ), - ), - ], - ), - ), - const Divider(), - ], - ); - } -} diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index b85bcd5..85b696c 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import '../bindings/home_binding.dart'; import '../pages/donation/donation_page.dart'; import '../pages/home/base/home_page.dart'; +import '../pages/home/notification/notification_page.dart'; import '../pages/intro/intro_page.dart'; import '../pages/recording/recording_page.dart'; import '../pages/save_video/save_video_page.dart'; @@ -12,6 +13,7 @@ part './app_routes.dart'; class AppPages { static final pages = [ GetPage(name: Routes.HOME, page: () => HomePage(), binding: HomeBinding()), + GetPage(name: Routes.NOTIFICATION, page: () => const NotificationPage()), GetPage(name: Routes.INTRO, page: () => IntroPage()), GetPage(name: Routes.RECORDING, page: () => RecordingPage()), GetPage(name: Routes.DONATION, page: () => DonationPage()), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index b6cf7ed..9045604 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -3,6 +3,7 @@ part of 'app_pages.dart'; abstract class Routes { static const INTRO = '/intro'; static const HOME = '/home'; + static const NOTIFICATION = '/home/notification'; static const RECORDING = '/recording'; static const DONATION = '/donation'; static const SAVE_VIDEO = '/save_video'; diff --git a/lib/utils/theme.dart b/lib/utils/theme.dart index 3902b09..eea98c0 100644 --- a/lib/utils/theme.dart +++ b/lib/utils/theme.dart @@ -16,6 +16,15 @@ class Themes { colorScheme: ColorScheme.fromSwatch().copyWith( secondary: AppColors.mainColor, ), + timePickerTheme: const TimePickerThemeData( + backgroundColor: AppColors.light, + hourMinuteTextColor: AppColors.dark, + dialTextColor: AppColors.dark, + dialHandColor: AppColors.mainColor, + hourMinuteColor: Colors.white38, + dayPeriodTextColor: AppColors.mainColor, + entryModeIconColor: AppColors.dark, + ), ); static final dark = ThemeData.dark().copyWith( @@ -27,6 +36,12 @@ class Themes { colorScheme: ColorScheme.fromSwatch().copyWith( secondary: AppColors.mainColor, ), + timePickerTheme: const TimePickerThemeData( + backgroundColor: AppColors.dark, + hourMinuteTextColor: AppColors.light, + hourMinuteColor: Colors.black38, + dayPeriodTextColor: AppColors.light, + ), ); } From 9cba0fa8f72198f2b89477358c06ad0954fef1b1 Mon Sep 17 00:00:00 2001 From: David <87503695+daoxve@users.noreply.github.com> Date: Sat, 10 Dec 2022 17:51:36 +0100 Subject: [PATCH 3/4] Add toast feedback for test notification --- .../widgets/switch_notifications.dart | 11 ++++ pubspec.lock | 7 +++ pubspec.yaml | 50 +++++++++---------- 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/lib/pages/home/notification/widgets/switch_notifications.dart b/lib/pages/home/notification/widgets/switch_notifications.dart index 6246384..0d0d43a 100644 --- a/lib/pages/home/notification/widgets/switch_notifications.dart +++ b/lib/pages/home/notification/widgets/switch_notifications.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:timezone/data/latest.dart' as tz; @@ -115,6 +116,16 @@ class _SwitchNotificationsComponentState extends State=2.18.2 <3.0.0' dependencies: - flutter: - sdk: flutter - - get: + about: null + avatar_glow: null camera: git: url: https://github.com/KyleKun/plugins.git path: packages/camera/camera/ - flutter_colorpicker: - path_provider: - shared_preferences: - flutter_ffmpeg: - permission_handler: - # TODO: remove this in the future & reference the official package when higher kotlin versions are supported + flutter: + sdk: flutter + flutter_colorpicker: null + flutter_ffmpeg: null + flutter_local_notifications: null + fluttertoast: ^8.1.1 + get: null + group_radio_button: null + introduction_screen: null + native_device_orientation: null + open_file: null + path_provider: null + permission_handler: null + rive: null + salomon_bottom_bar: null + share: null + shared_preferences: null tapioca: git: url: https://github.com/daoxve/tapioca.git - ref: android-dependency-patch # branch name - salomon_bottom_bar: - introduction_screen: - avatar_glow: - url_launcher: - rive: - native_device_orientation: - video_player: - about: - share: - open_file: - group_radio_button: - flutter_local_notifications: - timezone: - timer_builder: + ref: android-dependency-patch + timer_builder: null + timezone: null + url_launcher: null + video_player: null #logger: - dev_dependencies: flutter_test: sdk: flutter From e8ff05839b0e6b59d6f2c121fe47fe470b9ed861 Mon Sep 17 00:00:00 2001 From: David <87503695+daoxve@users.noreply.github.com> Date: Sat, 10 Dec 2022 22:25:32 +0100 Subject: [PATCH 4/4] Fully integrate scheduled notifications --- .../widgets/switch_notifications.dart | 41 +++++++------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/lib/pages/home/notification/widgets/switch_notifications.dart b/lib/pages/home/notification/widgets/switch_notifications.dart index 0d0d43a..c94d3c3 100644 --- a/lib/pages/home/notification/widgets/switch_notifications.dart +++ b/lib/pages/home/notification/widgets/switch_notifications.dart @@ -19,8 +19,7 @@ class SwitchNotificationsComponent extends StatefulWidget { } class _SwitchNotificationsComponentState extends State { - final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); + final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); final int notificationId = 1; @@ -71,35 +70,23 @@ class _SwitchNotificationsComponentState extends State scheduleNotification() async { - /// Get current hour - final hour = DateTime.now().hour; - - /// Since there isn't a way to choose the best time to display, it will be shown around 18pm - const int defaultNotificationHour = 18; - - /// Difference between current hour and 12am to calculate scheduling - int difference = 0; - - /// Final hour distance that notification will be shown - int notificationHour = 0; - - if (hour >= defaultNotificationHour) { - difference = hour - defaultNotificationHour; - notificationHour = 24 - difference; - } else { - difference = defaultNotificationHour - hour; - notificationHour = difference; - } - - // print('notification will be shown in: $notificationHour hours from now'); + final now = DateTime.now(); + + // sets the scheduled time in DateTime format + final String setTime = DateTime( + now.year, + now.month, + now.day, + scheduledTimeOfDay.hour, + scheduledTimeOfDay.minute, + ).toString(); /// Schedule notification - flutterLocalNotificationsPlugin.zonedSchedule( + await flutterLocalNotificationsPlugin.zonedSchedule( notificationId, 'notificationTitle'.tr, 'notificationBody'.tr, - tz.TZDateTime.now(tz.local).add(Duration(hours: notificationHour)), - // tz.TZDateTime.now(tz.local).add(const Duration(seconds: 3)), + tz.TZDateTime.parse(tz.local, setTime), platformNotificationDetails, uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, @@ -234,6 +221,8 @@ class _SwitchNotificationsComponentState extends State