From adb17787107ee2687bd066d624912d8d24fc313e Mon Sep 17 00:00:00 2001 From: Garrett Watson Date: Fri, 10 Nov 2023 18:40:58 -0500 Subject: [PATCH] chore: update packages + update build config + add audio playback on iOS/macOS + swap downloader library --- ios/Podfile | 5 +- ios/Podfile.lock | 56 +- ios/Runner.xcodeproj/project.pbxproj | 64 +- .../xcshareddata/xcschemes/Runner.xcscheme | 15 +- ios/Runner/Info.plist | 10 +- ios/Runner/Runner.entitlements | 10 + lib/clients/audio_cache_client.dart | 29 +- lib/clients/notification_client.dart | 120 +-- lib/main.dart | 96 ++- .../inventory/inventory_browser_app_bar.dart | 158 ++-- lib/widgets/login_screen.dart | 79 +- .../messages/message_audio_player.dart | 64 +- lib/widgets/messages/message_input_bar.dart | 765 ++++++++++-------- lib/widgets/messages/messages_list.dart | 36 +- .../messages/messages_session_header.dart | 16 +- macos/Flutter/GeneratedPluginRegistrant.swift | 10 +- macos/Podfile | 4 + macos/Podfile.lock | 36 +- macos/Runner.xcodeproj/project.pbxproj | 38 +- .../xcschemes/Flutter Assemble.xcscheme | 72 ++ .../xcshareddata/xcschemes/Runner.xcscheme | 4 +- macos/Runner/DebugProfile.entitlements | 4 + macos/Runner/Release.entitlements | 4 + macos/Runner/RunnerDebug.entitlements | 20 + pubspec.lock | 136 ++-- pubspec.yaml | 32 +- 26 files changed, 1197 insertions(+), 686 deletions(-) create mode 100644 ios/Runner/Runner.entitlements create mode 100644 macos/Runner.xcodeproj/xcshareddata/xcschemes/Flutter Assemble.xcscheme create mode 100644 macos/Runner/RunnerDebug.entitlements diff --git a/ios/Podfile b/ios/Podfile index 164df53..9a3af86 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '12.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -40,5 +40,8 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' + end end end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3a1c842..5924ee2 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,6 +1,8 @@ PODS: - audio_session (0.0.1): - Flutter + - background_downloader (0.0.1): + - Flutter - camera_avfoundation (0.0.1): - Flutter - DKImagePickerController/Core (4.3.4): @@ -34,12 +36,17 @@ PODS: - DKPhotoGallery/Resource (0.0.17): - SDWebImage - SwiftyGif + - ffmpeg-kit-ios-audio (6.0) + - ffmpeg_kit_flutter_audio (6.0.3): + - ffmpeg_kit_flutter_audio/audio (= 6.0.3) + - Flutter + - ffmpeg_kit_flutter_audio/audio (6.0.3): + - ffmpeg-kit-ios-audio (= 6.0) + - Flutter - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter - Flutter (1.0.0) - - flutter_downloader (0.0.1): - - Flutter - flutter_local_notifications (0.0.1): - Flutter - flutter_secure_storage (6.0.0): @@ -56,16 +63,17 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - permission_handler_apple (9.0.4): + - permission_handler_apple (9.1.1): - Flutter - - record (0.0.1): + - record_darwin (1.0.0): - Flutter + - FlutterMacOS - SDWebImage (5.13.2): - SDWebImage/Core (= 5.13.2) - SDWebImage/Core (5.13.2) - share_plus (0.0.1): - Flutter - - sqflite (0.0.2): + - sqflite (0.0.3): - Flutter - FMDB (>= 2.7.5) - SwiftyGif (5.4.4) @@ -76,10 +84,11 @@ PODS: DEPENDENCIES: - audio_session (from `.symlinks/plugins/audio_session/ios`) + - background_downloader (from `.symlinks/plugins/background_downloader/ios`) - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`) + - ffmpeg_kit_flutter_audio (from `.symlinks/plugins/ffmpeg_kit_flutter_audio/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - - flutter_downloader (from `.symlinks/plugins/flutter_downloader/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) @@ -87,7 +96,7 @@ DEPENDENCIES: - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - - record (from `.symlinks/plugins/record/ios`) + - record_darwin (from `.symlinks/plugins/record_darwin/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -97,6 +106,7 @@ SPEC REPOS: trunk: - DKImagePickerController - DKPhotoGallery + - ffmpeg-kit-ios-audio - FMDB - SDWebImage - SwiftyGif @@ -104,14 +114,16 @@ SPEC REPOS: EXTERNAL SOURCES: audio_session: :path: ".symlinks/plugins/audio_session/ios" + background_downloader: + :path: ".symlinks/plugins/background_downloader/ios" camera_avfoundation: :path: ".symlinks/plugins/camera_avfoundation/ios" + ffmpeg_kit_flutter_audio: + :path: ".symlinks/plugins/ffmpeg_kit_flutter_audio/ios" file_picker: :path: ".symlinks/plugins/file_picker/ios" Flutter: :path: Flutter - flutter_downloader: - :path: ".symlinks/plugins/flutter_downloader/ios" flutter_local_notifications: :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_secure_storage: @@ -126,8 +138,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" - record: - :path: ".symlinks/plugins/record/ios" + record_darwin: + :path: ".symlinks/plugins/record_darwin/ios" share_plus: :path: ".symlinks/plugins/share_plus/ios" sqflite: @@ -139,28 +151,30 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: audio_session: 4f3e461722055d21515cf3261b64c973c062f345 + background_downloader: 6f55e5548875be2ad4bb91b542558b5be22f339a camera_avfoundation: 3125e8cd1a4387f6f31c6c63abb8a55892a9eeeb DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: ce3938a0df3cc1ef404671531facef740d03f920 + ffmpeg-kit-ios-audio: 9fa9953fc197280a69e59c603c7fa7690df7190c + ffmpeg_kit_flutter_audio: 9b107d9902e16804c90637cd7f42106a5447a9e6 + file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - flutter_downloader: b7301ae057deadd4b1650dc7c05375f10ff12c39 flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa - package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 - permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce - record: cae05d8dd3cdb1dea3511b20e5a5811a1ae00d0d + package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 + record_darwin: 1f6619f2abac4d1ca91d3eeab038c980d76f1517 SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866 - share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028 - sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 + share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 + sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f - url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 + url_launcher_ios: 68d46cc9766d0c41dbdc884310529557e3cd7a86 workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6 -PODFILE CHECKSUM: 7be2f5f74864d463a8ad433546ed1de7e0f29aef +PODFILE CHECKSUM: 0a7d5b7d0e53420cb0284f7b2f171f93843b94d2 COCOAPODS: 1.12.1 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 1c146c0..88f7ac5 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -10,7 +10,7 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 63CD0BA48BE3D8D0222E5DCF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3BAB80CE2FD566CD74754C6 /* Pods_Runner.framework */; }; + 3EACF4502AF94B2E0009EB00 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3BAB80CE2FD566CD74754C6 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -28,19 +28,6 @@ }; /* End PBXContainerItemProxy section */ -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - /* 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 = ""; }; @@ -48,6 +35,8 @@ 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 35345364120A3EBED9C200D8 /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3EACF44C2AF946870009EB00 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 3EACF44D2AF94B1B0009EB00 /* sqflite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = sqflite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6357E70700B420135CF38106 /* 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 = ""; }; 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 = ""; }; @@ -56,7 +45,7 @@ 947052A3147FEB296CDB1CF8 /* 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 = ""; }; 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; }; + 97C146EE1CF9000F007C117D /* ReCon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReCon.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 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 = ""; }; @@ -80,7 +69,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 63CD0BA48BE3D8D0222E5DCF /* Pods_Runner.framework in Frameworks */, + 3EACF4502AF94B2E0009EB00 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -121,7 +110,7 @@ 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, + 97C146EE1CF9000F007C117D /* ReCon.app */, 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; @@ -130,6 +119,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 3EACF44C2AF946870009EB00 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -158,6 +148,7 @@ F90E3A4B697A7FB1786B0BCF /* Frameworks */ = { isa = PBXGroup; children = ( + 3EACF44D2AF94B1B0009EB00 /* sqflite.framework */, C3BAB80CE2FD566CD74754C6 /* Pods_Runner.framework */, 8B83C597EDF1CEFE95FFFB1B /* Pods_RunnerTests.framework */, ); @@ -195,7 +186,6 @@ 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, BDF85620D00D0FE7A8BAEF7B /* [CP] Embed Pods Frameworks */, ); @@ -205,7 +195,7 @@ ); name = Runner; productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productReference = 97C146EE1CF9000F007C117D /* ReCon.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -453,7 +443,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + INFOPLIST_KEY_CFBundleDisplayName = ReCon; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; @@ -468,21 +460,23 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = P9AV4LPNLL; ENABLE_BITCODE = NO; + FLUTTER_BUILD_NAME = 0.10.3; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ReCon; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 0.10.0; + MARKETING_VERSION = 0.10.3; PRODUCT_BUNDLE_IDENTIFIER = me.voidspace.recon; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = ReCon; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -587,7 +581,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + INFOPLIST_KEY_CFBundleDisplayName = ReCon; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -637,7 +633,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + INFOPLIST_KEY_CFBundleDisplayName = ReCon; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; @@ -654,21 +652,23 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = P9AV4LPNLL; ENABLE_BITCODE = NO; + FLUTTER_BUILD_NAME = 0.10.3; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ReCon; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 0.10.0; + MARKETING_VERSION = 0.10.3; PRODUCT_BUNDLE_IDENTIFIER = me.voidspace.recon; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = ReCon; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -682,21 +682,23 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = P9AV4LPNLL; ENABLE_BITCODE = NO; + FLUTTER_BUILD_NAME = 0.10.3; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ReCon; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 0.10.0; + MARKETING_VERSION = 0.10.3; PRODUCT_BUNDLE_IDENTIFIER = me.voidspace.recon; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = ReCon; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a0..30e0c22 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -15,7 +15,7 @@ @@ -31,7 +31,7 @@ @@ -51,9 +51,9 @@ @@ -82,7 +82,7 @@ @@ -93,6 +93,7 @@ diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 21d62a7..7b71a45 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -7,7 +7,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Recon + ReCon CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -15,7 +15,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - recon + ReCon CFBundlePackageType APPL CFBundleShortVersionString @@ -28,6 +28,12 @@ UIApplicationSupportsIndirectInputEvents + UIBackgroundModes + + fetch + processing + remote-notification + UILaunchStoryboardName LaunchScreen.storyboard UIMainStoryboardFile diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..39f5fef --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,10 @@ + + + + + keychain-access-groups + + $(AppIdentifierPrefix)me.voidspace.recon + + + diff --git a/lib/clients/audio_cache_client.dart b/lib/clients/audio_cache_client.dart index 3954444..bb461df 100644 --- a/lib/clients/audio_cache_client.dart +++ b/lib/clients/audio_cache_client.dart @@ -1,24 +1,41 @@ import 'dart:io'; -import 'package:recon/auxiliary.dart'; -import 'package:recon/clients/api_client.dart'; +import 'package:ffmpeg_kit_flutter_audio/ffmpeg_kit.dart'; import 'package:http/http.dart' as http; -import 'package:recon/models/message.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:recon/auxiliary.dart'; +import 'package:recon/clients/api_client.dart'; +import 'package:recon/models/message.dart'; class AudioCacheClient { final Future _directoryFuture = getTemporaryDirectory(); + final bool _isDarwin = Platform.isMacOS || Platform.isIOS; Future cachedNetworkAudioFile(AudioClipContent clip) async { final directory = await _directoryFuture; - final file = File("${directory.path}/${basename(clip.assetUri)}"); + final fileName = basenameWithoutExtension(clip.assetUri); + final file = File("${directory.path}/$fileName.ogg"); if (!await file.exists()) { await file.create(recursive: true); - final response = await http.get(Uri.parse(Aux.resdbToHttp(clip.assetUri))); + final response = + await http.get(Uri.parse(Aux.resdbToHttp(clip.assetUri))); ApiClient.checkResponseCode(response); await file.writeAsBytes(response.bodyBytes); } + if (_isDarwin) { + final wavFile = File("${directory.path}/$fileName.wav"); + final wavFileExists = await wavFile.exists(); + if (wavFileExists && await wavFile.length() == 0) { + await wavFile.delete(); + } + if (!wavFileExists) { + await wavFile.create(recursive: true); + await FFmpegKit.executeAsync( + "-y -acodec libvorbis -i ${file.path} -acodec pcm_s16le ${wavFile.path}"); + } + return wavFile; + } return file; } -} \ No newline at end of file +} diff --git a/lib/clients/notification_client.dart b/lib/clients/notification_client.dart index 831fac9..874c6fa 100644 --- a/lib/clients/notification_client.dart +++ b/lib/clients/notification_client.dart @@ -1,17 +1,19 @@ import 'dart:convert'; +import 'package:collection/collection.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart' + as fln; import 'package:recon/auxiliary.dart'; import 'package:recon/models/message.dart'; import 'package:recon/models/session.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart' as fln; -import 'package:collection/collection.dart'; class NotificationChannel { final String id; final String name; final String description; - const NotificationChannel({required this.name, required this.id, required this.description}); + const NotificationChannel( + {required this.name, required this.id, required this.description}); } class NotificationClient { @@ -21,14 +23,18 @@ class NotificationClient { description: "Messages received from your friends", ); - final fln.FlutterLocalNotificationsPlugin _notifier = fln.FlutterLocalNotificationsPlugin() - ..initialize( - const fln.InitializationSettings( + final fln.FlutterLocalNotificationsPlugin _notifier = + fln.FlutterLocalNotificationsPlugin() + ..initialize(const fln.InitializationSettings( android: fln.AndroidInitializationSettings("ic_notification"), - ) - ); + iOS: fln.DarwinInitializationSettings(), + macOS: fln.DarwinInitializationSettings(), + linux: + fln.LinuxInitializationSettings(defaultActionName: "Open ReCon"), + )); - Future showUnreadMessagesNotification(Iterable messages) async { + Future showUnreadMessagesNotification( + Iterable messages) async { if (messages.isEmpty) return; final bySender = groupBy(messages, (p0) => p0.senderId); @@ -39,56 +45,58 @@ class NotificationClient { uname.hashCode, null, null, - fln.NotificationDetails(android: fln.AndroidNotificationDetails( - _messageChannel.id, - _messageChannel.name, - channelDescription: _messageChannel.description, - importance: fln.Importance.high, - priority: fln.Priority.max, - actions: [], //TODO: Make clicking message notification open chat of specified user. - styleInformation: fln.MessagingStyleInformation( - fln.Person( - name: uname, - bot: false, + fln.NotificationDetails( + android: fln.AndroidNotificationDetails( + _messageChannel.id, + _messageChannel.name, + channelDescription: _messageChannel.description, + importance: fln.Importance.high, + priority: fln.Priority.max, + actions: [], //TODO: Make clicking message notification open chat of specified user. + styleInformation: fln.MessagingStyleInformation( + fln.Person( + name: uname, + bot: false, + ), + groupConversation: false, + messages: entry.value.map((message) { + String content; + switch (message.type) { + case MessageType.unknown: + content = "Unknown Message Type"; + break; + case MessageType.text: + content = message.content; + break; + case MessageType.sound: + content = "Audio Message"; + break; + case MessageType.sessionInvite: + try { + final session = + Session.fromMap(jsonDecode(message.content)); + content = "Session Invite to ${session.name}"; + } catch (e) { + content = "Session Invite"; + } + break; + case MessageType.object: + content = "Asset"; + break; + } + return fln.Message( + content, + message.sendTime.toLocal(), + fln.Person( + name: uname, + bot: false, + ), + ); + }).toList(), ), - groupConversation: false, - messages: entry.value.map((message) { - String content; - switch (message.type) { - case MessageType.unknown: - content = "Unknown Message Type"; - break; - case MessageType.text: - content = message.content; - break; - case MessageType.sound: - content = "Audio Message"; - break; - case MessageType.sessionInvite: - try { - final session = Session.fromMap(jsonDecode(message.content)); - content = "Session Invite to ${session.name}"; - } catch (e) { - content = "Session Invite"; - } - break; - case MessageType.object: - content = "Asset"; - break; - } - return fln.Message( - content, - message.sendTime.toLocal(), - fln.Person( - name: uname, - bot: false, - ), - ); - }).toList(), ), ), - ), ); } } -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index 6257836..98493e2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,12 @@ import 'dart:developer'; +import 'dart:isolate'; +import 'dart:ui'; +import 'package:background_downloader/background_downloader.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_phoenix/flutter_phoenix.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:intl/intl.dart'; @@ -28,10 +30,6 @@ import 'models/authentication_data.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - // await FlutterDownloader.initialize( - // debug: kDebugMode, - // ); - SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( systemStatusBarContrastEnforced: true, @@ -40,30 +38,40 @@ void main() async { ), ); - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: [SystemUiOverlay.top]); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, + overlays: [SystemUiOverlay.top]); await Hive.initFlutter(); final dateFormat = DateFormat.Hms(); - Logger.root.onRecord.listen( - (event) => log("${dateFormat.format(event.time)}: ${event.message}", name: event.loggerName, time: event.time)); + Logger.root.onRecord.listen((event) => log( + "${dateFormat.format(event.time)}: ${event.message}", + name: event.loggerName, + time: event.time)); final settingsClient = SettingsClient(); await settingsClient.loadSettings(); - final newSettings = - settingsClient.currentSettings.copyWith(machineId: settingsClient.currentSettings.machineId.valueOrDefault); - await settingsClient.changeSettings(newSettings); // Save generated machineId to disk + final newSettings = settingsClient.currentSettings.copyWith( + machineId: settingsClient.currentSettings.machineId.valueOrDefault); + await settingsClient + .changeSettings(newSettings); // Save generated machineId to disk AuthenticationData cachedAuth = AuthenticationData.unauthenticated(); try { cachedAuth = await ApiClient.tryCachedLogin(); - } catch (_) {} + } catch (_) { + // Ignore + } - runApp(ReCon(settingsClient: settingsClient, cachedAuthentication: cachedAuth)); + runApp( + ReCon(settingsClient: settingsClient, cachedAuthentication: cachedAuth)); } class ReCon extends StatefulWidget { - const ReCon({required this.settingsClient, required this.cachedAuthentication, super.key}); + const ReCon( + {required this.settingsClient, + required this.cachedAuthentication, + super.key}); final SettingsClient settingsClient; final AuthenticationData cachedAuthentication; @@ -73,7 +81,9 @@ class ReCon extends StatefulWidget { } class _ReConState extends State { - final Typography _typography = Typography.material2021(platform: TargetPlatform.android); + final Typography _typography = + Typography.material2021(platform: defaultTargetPlatform); + final ReceivePort _port = ReceivePort(); late AuthenticationData _authData = widget.cachedAuthentication; bool _checkedForUpdate = false; @@ -95,7 +105,8 @@ class _ReConState extends State { } try { - lastDismissedSem = SemVer.fromString(settings.currentSettings.lastDismissedVersion.valueOrDefault); + lastDismissedSem = SemVer.fromString( + settings.currentSettings.lastDismissedVersion.valueOrDefault); } catch (_) { lastDismissedSem = SemVer.zero(); } @@ -110,7 +121,9 @@ class _ReConState extends State { return; } - if (remoteSem > currentSem && navigator.overlay?.context != null && context.mounted) { + if (remoteSem > currentSem && + navigator.overlay?.context != null && + context.mounted) { showDialog( context: navigator.overlay!.context, builder: (context) { @@ -124,6 +137,31 @@ class _ReConState extends State { }); } + @override + void initState() { + super.initState(); + + IsolateNameServer.registerPortWithName( + _port.sendPort, 'downloader_send_port'); + _port.listen((dynamic data) { + // Not useful yet? idk... + // String id = data[0]; + // DownloadTaskStatus status = data[1]; + // int progress = data[2]; + }); + + FileDownloader().updates.listen(downloadCallback); + } + + @override + void dispose() { + IsolateNameServer.removePortNameMapping('downloader_send_port'); + super.dispose(); + } + + @pragma('vm:entry-point') + static void downloadCallback(TaskUpdate event) {} + @override Widget build(BuildContext context) { return Phoenix( @@ -138,21 +176,26 @@ class _ReConState extends State { Phoenix.rebirth(context); }, child: DynamicColorBuilder( - builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) => MaterialApp( + builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) => + MaterialApp( debugShowCheckedModeBanner: false, title: 'ReCon', theme: ThemeData( useMaterial3: true, textTheme: _typography.black, - colorScheme: - lightDynamic ?? ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.light), + colorScheme: lightDynamic ?? + ColorScheme.fromSeed( + seedColor: Colors.purple, brightness: Brightness.light), ), darkTheme: ThemeData( useMaterial3: true, textTheme: _typography.white, - colorScheme: darkDynamic ?? ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.dark), + colorScheme: darkDynamic ?? + ColorScheme.fromSeed( + seedColor: Colors.purple, brightness: Brightness.dark), ), - themeMode: ThemeMode.values[widget.settingsClient.currentSettings.themeMode.valueOrDefault], + themeMode: ThemeMode.values[widget + .settingsClient.currentSettings.themeMode.valueOrDefault], home: Builder( // Builder is necessary here since we need a context which has access to the ClientHolder builder: (context) { @@ -165,7 +208,8 @@ class _ReConState extends State { create: (context) => MessagingClient( apiClient: clientHolder.apiClient, settingsClient: clientHolder.settingsClient, - notificationClient: clientHolder.notificationClient, + notificationClient: + clientHolder.notificationClient, ), ), ChangeNotifierProvider( @@ -182,13 +226,15 @@ class _ReConState extends State { ], child: AnnotatedRegion( value: SystemUiOverlayStyle( - statusBarColor: Theme.of(context).colorScheme.surfaceVariant, + statusBarColor: + Theme.of(context).colorScheme.surfaceVariant, ), child: const Home(), ), ) : LoginScreen( - onLoginSuccessful: (AuthenticationData authData) async { + onLoginSuccessful: + (AuthenticationData authData) async { if (authData.isAuthenticated) { setState(() { _authData = authData; diff --git a/lib/widgets/inventory/inventory_browser_app_bar.dart b/lib/widgets/inventory/inventory_browser_app_bar.dart index 040b545..4a83366 100644 --- a/lib/widgets/inventory/inventory_browser_app_bar.dart +++ b/lib/widgets/inventory/inventory_browser_app_bar.dart @@ -1,11 +1,13 @@ +import 'dart:io'; import 'dart:isolate'; import 'dart:ui'; +import 'package:background_downloader/background_downloader.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:intl/intl.dart'; import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import 'package:recon/auxiliary.dart'; import 'package:recon/clients/inventory_client.dart'; @@ -19,32 +21,16 @@ class InventoryBrowserAppBar extends StatefulWidget { } class _InventoryBrowserAppBarState extends State { - final ReceivePort _port = ReceivePort(); - - @override - void initState() { - super.initState(); - - IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port'); - _port.listen((dynamic data) { - // Not useful yet? idk... - // String id = data[0]; - // DownloadTaskStatus status = data[1]; - // int progress = data[2]; - }); - - FlutterDownloader.registerCallback(downloadCallback); - } - - @override - void dispose() { - IsolateNameServer.removePortNameMapping('downloader_send_port'); - super.dispose(); - } + final Future _tempDirectoryFuture = getTemporaryDirectory(); @pragma('vm:entry-point') - static void downloadCallback(String id, int status, int progress) { - final SendPort? send = IsolateNameServer.lookupPortByName('downloader_send_port'); + static void downloadCallback(TaskUpdate event) { + final id = event.task.taskId; + final status = event is TaskStatusUpdate ? event.status : null; + final progress = event is TaskProgressUpdate ? event.progress : null; + final SendPort? send = + IsolateNameServer.lookupPortByName('downloader_send_port'); + send?.send([id, status, progress]); } @@ -88,7 +74,9 @@ class _InventoryBrowserAppBarState extends State { style: TextStyle( color: iClient.sortReverse == false ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface, + : Theme.of(context) + .colorScheme + .onSurface, ), ), ], @@ -101,7 +89,9 @@ class _InventoryBrowserAppBarState extends State { Icon(Icons.arrow_downward, color: iClient.sortReverse == true ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface), + : Theme.of(context) + .colorScheme + .onSurface), const SizedBox( width: 8, ), @@ -110,7 +100,9 @@ class _InventoryBrowserAppBarState extends State { style: TextStyle( color: iClient.sortReverse == true ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface, + : Theme.of(context) + .colorScheme + .onSurface, ), ), ], @@ -136,18 +128,27 @@ class _InventoryBrowserAppBarState extends State { Icon( e.icon, color: iClient.sortMode == e - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface, + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .onSurface, ), const SizedBox( width: 8, ), Text( - toBeginningOfSentenceCase(e.name) ?? e.name, + toBeginningOfSentenceCase(e.name) ?? + e.name, style: TextStyle( color: iClient.sortMode == e - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface, + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .onSurface, ), ) ], @@ -172,7 +173,8 @@ class _InventoryBrowserAppBarState extends State { actions: [ if (iClient.selectedRecordCount == 1 && (iClient.selectedRecords.firstOrNull?.isLink == true || - iClient.selectedRecords.firstOrNull?.isItem == true)) + iClient.selectedRecords.firstOrNull?.isItem == + true)) IconButton( onPressed: () { Share.share(iClient.selectedRecords.first.assetUri); @@ -184,8 +186,12 @@ class _InventoryBrowserAppBarState extends State { onPressed: () async { final selectedRecords = iClient.selectedRecords; - final assetUris = selectedRecords.map((record) => record.assetUri).toList(); - final thumbUris = selectedRecords.map((record) => record.thumbnailUri).toList(); + final assetUris = selectedRecords + .map((record) => record.assetUri) + .toList(); + final thumbUris = selectedRecords + .map((record) => record.thumbnailUri) + .toList(); final selectedUris = await showDialog>( context: context, @@ -226,7 +232,8 @@ class _InventoryBrowserAppBarState extends State { ); if (selectedUris == null) return; - final directory = await FilePicker.platform.getDirectoryPath(dialogTitle: "Download to..."); + final directory = await FilePicker.platform + .getDirectoryPath(dialogTitle: "Download to..."); if (directory == null) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( @@ -241,22 +248,54 @@ class _InventoryBrowserAppBarState extends State { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text("Selected directory is invalid"), + content: + Text("Selected directory is invalid"), ), ); } return; } for (var record in selectedRecords) { - final uri = selectedUris == thumbUris ? record.thumbnailUri : record.assetUri; - await FlutterDownloader.enqueue( + final uri = selectedUris == thumbUris + ? record.thumbnailUri + : record.assetUri; + final filename = + "${record.id.split("-")[1]}-${record.formattedName.toString()}${extension(uri)}"; + final downloadTask = DownloadTask( url: Aux.resdbToHttp(uri), - savedDir: directory, - showNotification: true, - openFileFromNotification: false, - fileName: - "${record.id.split("-")[1]}-${record.formattedName.toString()}${extension(uri)}", + allowPause: true, + baseDirectory: BaseDirectory.temporary, + filename: filename, + updates: Updates.statusAndProgress, ); + await FileDownloader() + .enqueue(downloadTask) + .then((b) { + if (context.mounted) { + if (b) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Downloaded ${record.formattedName.toString()}"), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Failed to download ${record.formattedName.toString()}"), + ), + ); + } + } + }); + final tempDirectory = await _tempDirectoryFuture; + final file = File( + "${tempDirectory.path}/${record.id.split("-")[1]}-${record.formattedName.toString()}${extension(uri)}"); + if (await file.exists()) { + final newFile = File("$directory/$filename"); + await file.rename(newFile.path); + } } iClient.clearSelectedRecords(); }, @@ -278,8 +317,10 @@ class _InventoryBrowserAppBarState extends State { title: Text(iClient.selectedRecordCount == 1 ? "Really delete this Record?" : "Really delete ${iClient.selectedRecordCount} Records?"), - content: const Text("This action cannot be undone!"), - actionsAlignment: MainAxisAlignment.spaceBetween, + content: const Text( + "This action cannot be undone!"), + actionsAlignment: + MainAxisAlignment.spaceBetween, actions: [ TextButton( onPressed: loading @@ -295,7 +336,8 @@ class _InventoryBrowserAppBarState extends State { if (loading) const SizedBox.square( dimension: 16, - child: CircularProgressIndicator(strokeWidth: 2), + child: CircularProgressIndicator( + strokeWidth: 2), ), const SizedBox( width: 4, @@ -308,12 +350,16 @@ class _InventoryBrowserAppBarState extends State { loading = true; }); try { - await iClient.deleteSelectedRecords(); + await iClient + .deleteSelectedRecords(); } catch (e) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( + ScaffoldMessenger.of( + context) + .showSnackBar( SnackBar( - content: Text("Failed to delete one or more records: $e"), + content: Text( + "Failed to delete one or more records: $e"), ), ); } @@ -322,12 +368,16 @@ class _InventoryBrowserAppBarState extends State { }); } if (context.mounted) { - Navigator.of(context).pop(true); + Navigator.of(context) + .pop(true); } - iClient.reloadCurrentDirectory(); + iClient + .reloadCurrentDirectory(); }, style: TextButton.styleFrom( - foregroundColor: Theme.of(context).colorScheme.error, + foregroundColor: Theme.of(context) + .colorScheme + .error, ), child: const Text("Delete"), ), diff --git a/lib/widgets/login_screen.dart b/lib/widgets/login_screen.dart index f974807..ac82ab7 100644 --- a/lib/widgets/login_screen.dart +++ b/lib/widgets/login_screen.dart @@ -1,9 +1,11 @@ -import 'package:recon/clients/api_client.dart'; -import 'package:recon/models/authentication_data.dart'; +import 'dart:io'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:recon/client_holder.dart'; +import 'package:recon/clients/api_client.dart'; +import 'package:recon/models/authentication_data.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({this.onLoginSuccessful, this.cachedUsername, super.key}); @@ -81,8 +83,10 @@ class _LoginScreenState extends State { _error = "Please enter your 2FA-Code"; _totpFocusNode.requestFocus(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - _scrollController.animateTo(_scrollController.position.maxScrollExtent, - duration: const Duration(milliseconds: 400), curve: Curves.easeOutCirc); + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 400), + curve: Curves.easeOutCirc); }); } else { _error = "The given 2FA code is not valid."; @@ -111,25 +115,49 @@ class _LoginScreenState extends State { context: context, builder: (context) { return AlertDialog( - title: const Text("This app needs to ask your permission to send background notifications."), + title: const Text( + "This app needs to ask your permission to send background notifications."), content: const Text("Are you okay with that?"), actions: [ TextButton( onPressed: () async { Navigator.of(context).pop(); - await settingsClient - .changeSettings(settingsClient.currentSettings.copyWith(notificationsDenied: true)); + await settingsClient.changeSettings(settingsClient + .currentSettings + .copyWith(notificationsDenied: true)); }, child: const Text("No"), ), TextButton( onPressed: () async { Navigator.of(context).pop(); - final requestResult = await notificationManager - .resolvePlatformSpecificImplementation() - ?.requestPermission(); - await settingsClient.changeSettings(settingsClient.currentSettings - .copyWith(notificationsDenied: requestResult == null ? false : !requestResult)); + final requestResult = switch (Platform.operatingSystem) { + "android" => await notificationManager + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.requestNotificationsPermission(), + "fuschia" => + null, // "fuschia" is not supported by flutter_local_notifications + "ios" => await notificationManager + .resolvePlatformSpecificImplementation< + IOSFlutterLocalNotificationsPlugin>() + ?.requestPermissions( + alert: true, badge: true, sound: true), + "linux" => null, // don't want to deal with this right now + "macos" => await notificationManager + .resolvePlatformSpecificImplementation< + MacOSFlutterLocalNotificationsPlugin>() + ?.requestPermissions( + alert: true, badge: true, sound: true), + "windows" => + null, // also don't want to deal with this right now + _ => null, + }; + await settingsClient.changeSettings( + settingsClient.currentSettings.copyWith( + notificationsDenied: requestResult == null + ? false + : !requestResult)); }, child: const Text("Yes"), ) @@ -155,7 +183,8 @@ class _LoginScreenState extends State { Padding( padding: const EdgeInsets.symmetric(vertical: 64), child: Center( - child: Text("Sign In", style: Theme.of(context).textTheme.headlineMedium), + child: Text("Sign In", + style: Theme.of(context).textTheme.headlineMedium), ), ), Padding( @@ -164,7 +193,8 @@ class _LoginScreenState extends State { controller: _usernameController, onEditingComplete: () => _passwordFocusNode.requestFocus(), decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24), + contentPadding: + const EdgeInsets.symmetric(vertical: 20, horizontal: 24), border: OutlineInputBorder( borderRadius: BorderRadius.circular(32), ), @@ -180,22 +210,26 @@ class _LoginScreenState extends State { onEditingComplete: submit, obscureText: true, decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24), - border: OutlineInputBorder(borderRadius: BorderRadius.circular(32)), + contentPadding: + const EdgeInsets.symmetric(vertical: 20, horizontal: 24), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(32)), labelText: 'Password', ), ), ), if (_needsTotp) Padding( - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64), + padding: + const EdgeInsets.symmetric(vertical: 16, horizontal: 64), child: TextField( controller: _totpController, focusNode: _totpFocusNode, onEditingComplete: submit, obscureText: false, decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24), + contentPadding: const EdgeInsets.symmetric( + vertical: 20, horizontal: 24), border: OutlineInputBorder( borderRadius: BorderRadius.circular(32), ), @@ -218,8 +252,13 @@ class _LoginScreenState extends State { opacity: _errorOpacity, duration: const Duration(milliseconds: 200), child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64), - child: Text(_error, style: Theme.of(context).textTheme.labelMedium?.copyWith(color: Colors.red)), + padding: + const EdgeInsets.symmetric(vertical: 16, horizontal: 64), + child: Text(_error, + style: Theme.of(context) + .textTheme + .labelMedium + ?.copyWith(color: Colors.red)), ), ), ) diff --git a/lib/widgets/messages/message_audio_player.dart b/lib/widgets/messages/message_audio_player.dart index e5f6573..8d1e3b5 100644 --- a/lib/widgets/messages/message_audio_player.dart +++ b/lib/widgets/messages/message_audio_player.dart @@ -1,16 +1,16 @@ import 'dart:convert'; -import 'dart:io' show Platform; +import 'package:flutter/material.dart'; +import 'package:just_audio/just_audio.dart'; +import 'package:provider/provider.dart'; import 'package:recon/auxiliary.dart'; import 'package:recon/clients/audio_cache_client.dart'; import 'package:recon/models/message.dart'; import 'package:recon/widgets/messages/message_state_indicator.dart'; -import 'package:flutter/material.dart'; -import 'package:just_audio/just_audio.dart'; -import 'package:provider/provider.dart'; class MessageAudioPlayer extends StatefulWidget { - const MessageAudioPlayer({required this.message, this.foregroundColor, super.key}); + const MessageAudioPlayer( + {required this.message, this.foregroundColor, super.key}); final Message message; final Color? foregroundColor; @@ -19,7 +19,8 @@ class MessageAudioPlayer extends StatefulWidget { State createState() => _MessageAudioPlayerState(); } -class _MessageAudioPlayerState extends State with WidgetsBindingObserver { +class _MessageAudioPlayerState extends State + with WidgetsBindingObserver { final AudioPlayer _audioPlayer = AudioPlayer(); Future? _audioFileFuture; double _sliderValue = 0; @@ -42,7 +43,8 @@ class _MessageAudioPlayerState extends State with WidgetsBin super.didChangeDependencies(); final audioCache = Provider.of(context); _audioFileFuture = audioCache - .cachedNetworkAudioFile(AudioClipContent.fromMap(jsonDecode(widget.message.content))) + .cachedNetworkAudioFile( + AudioClipContent.fromMap(jsonDecode(widget.message.content))) .then((value) => _audioPlayer.setFilePath(value.path)) .whenComplete(() => _audioPlayer.setLoopMode(LoopMode.off)); } @@ -53,7 +55,8 @@ class _MessageAudioPlayerState extends State with WidgetsBin if (oldWidget.message.id == widget.message.id) return; final audioCache = Provider.of(context); _audioFileFuture = audioCache - .cachedNetworkAudioFile(AudioClipContent.fromMap(jsonDecode(widget.message.content))) + .cachedNetworkAudioFile( + AudioClipContent.fromMap(jsonDecode(widget.message.content))) .then((value) async { final path = _audioPlayer.setFilePath(value.path); await _audioPlayer.setLoopMode(LoopMode.off); @@ -88,7 +91,10 @@ class _MessageAudioPlayerState extends State with WidgetsBin textAlign: TextAlign.center, softWrap: true, maxLines: 3, - style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).colorScheme.error), + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: Theme.of(context).colorScheme.error), ), ], ), @@ -97,16 +103,13 @@ class _MessageAudioPlayerState extends State with WidgetsBin @override Widget build(BuildContext context) { - if (!Platform.isAndroid) { - return _createErrorWidget("Sorry, audio-messages are not\n supported on this platform."); - } - return IntrinsicWidth( child: StreamBuilder( stream: _audioPlayer.playerStateStream, builder: (context, snapshot) { if (snapshot.hasError) { - FlutterError.reportError(FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace)); + FlutterError.reportError(FlutterErrorDetails( + exception: snapshot.error!, stack: snapshot.stackTrace)); return _createErrorWidget("Failed to load audio-message."); } final playerState = snapshot.data; @@ -122,8 +125,12 @@ class _MessageAudioPlayerState extends State with WidgetsBin future: _audioFileFuture, builder: (context, fileSnapshot) { if (fileSnapshot.hasError) { + FlutterError.reportError(FlutterErrorDetails( + exception: fileSnapshot.error!, + stack: fileSnapshot.stackTrace)); return const IconButton( icon: Icon(Icons.warning), + tooltip: "Failed to load audio-message.", onPressed: null, ); } @@ -131,7 +138,8 @@ class _MessageAudioPlayerState extends State with WidgetsBin onPressed: fileSnapshot.hasData && snapshot.hasData && playerState != null && - playerState.processingState != ProcessingState.loading + playerState.processingState != + ProcessingState.loading ? () { switch (playerState.processingState) { case ProcessingState.idle: @@ -154,11 +162,15 @@ class _MessageAudioPlayerState extends State with WidgetsBin : null, color: widget.foregroundColor, icon: Icon( - ((_audioPlayer.duration ?? const Duration(days: 9999)) - _audioPlayer.position) + ((_audioPlayer.duration ?? + const Duration(days: 9999)) - + _audioPlayer.position) .inMilliseconds < 10 ? Icons.replay - : ((playerState?.playing ?? false) ? Icons.pause : Icons.play_arrow), + : ((playerState?.playing ?? false) + ? Icons.pause + : Icons.play_arrow), ), ); }, @@ -168,14 +180,16 @@ class _MessageAudioPlayerState extends State with WidgetsBin builder: (context, snapshot) { _sliderValue = _audioPlayer.duration == null ? 0 - : (_audioPlayer.position.inMilliseconds / (_audioPlayer.duration!.inMilliseconds)) + : (_audioPlayer.position.inMilliseconds / + (_audioPlayer.duration!.inMilliseconds)) .clamp(0, 1); return StatefulBuilder( // Not sure if this makes sense here... builder: (context, setState) { return SliderTheme( data: SliderThemeData( - inactiveTrackColor: widget.foregroundColor?.withAlpha(100), + inactiveTrackColor: + widget.foregroundColor?.withAlpha(100), ), child: Slider( thumbColor: widget.foregroundColor, @@ -189,7 +203,11 @@ class _MessageAudioPlayerState extends State with WidgetsBin }); _audioPlayer.seek( Duration( - milliseconds: (value * (_audioPlayer.duration?.inMilliseconds ?? 0)).round(), + milliseconds: (value * + (_audioPlayer + .duration?.inMilliseconds ?? + 0)) + .round(), ), ); }, @@ -213,10 +231,8 @@ class _MessageAudioPlayerState extends State with WidgetsBin builder: (context, snapshot) { return Text( "${snapshot.data?.format() ?? "??"}/${_audioPlayer.duration?.format() ?? "??"}", - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: widget.foregroundColor?.withAlpha(150)), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: widget.foregroundColor?.withAlpha(150)), ); }, ), diff --git a/lib/widgets/messages/message_input_bar.dart b/lib/widgets/messages/message_input_bar.dart index d0c793c..0f65d8d 100644 --- a/lib/widgets/messages/message_input_bar.dart +++ b/lib/widgets/messages/message_input_bar.dart @@ -2,27 +2,27 @@ import 'dart:convert'; import 'dart:io'; import 'package:collection/collection.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:provider/provider.dart'; import 'package:recon/apis/record_api.dart'; import 'package:recon/auxiliary.dart'; import 'package:recon/client_holder.dart'; import 'package:recon/clients/api_client.dart'; import 'package:recon/clients/messaging_client.dart'; -import 'package:recon/models/users/friend.dart'; import 'package:recon/models/message.dart'; +import 'package:recon/models/users/friend.dart'; import 'package:recon/widgets/messages/message_attachment_list.dart'; -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:provider/provider.dart'; import 'package:record/record.dart'; -import 'package:uuid/uuid.dart'; - class MessageInputBar extends StatefulWidget { - const MessageInputBar({this.disabled=false, required this.recipient, this.onMessageSent, super.key}); + const MessageInputBar( + {this.disabled = false, + required this.recipient, + this.onMessageSent, + super.key}); final bool disabled; final Friend recipient; @@ -35,7 +35,7 @@ class MessageInputBar extends StatefulWidget { class _MessageInputBarState extends State { final TextEditingController _messageTextController = TextEditingController(); final List<(FileType, File)> _loadedFiles = []; - final Record _recorder = Record(); + final AudioRecorder _recorder = AudioRecorder(); final ImagePicker _imagePicker = ImagePicker(); DateTime? _recordingStartTime; @@ -45,7 +45,8 @@ class _MessageInputBarState extends State { String _currentText = ""; double? _sendProgress; bool get _isRecording => _recordingStartTime != null; - set _isRecording(value) => _recordingStartTime = value ? DateTime.now() : null; + set _isRecording(value) => + _recordingStartTime = value ? DateTime.now() : null; bool _recordingCancelled = false; @override @@ -55,7 +56,8 @@ class _MessageInputBarState extends State { super.dispose(); } - Future sendTextMessage(ApiClient client, MessagingClient mClient, String content) async { + Future sendTextMessage( + ApiClient client, MessagingClient mClient, String content) async { if (content.isEmpty) return; final message = Message( id: Message.generateId(), @@ -69,7 +71,11 @@ class _MessageInputBarState extends State { mClient.sendMessage(message); } - Future sendImageMessage(ApiClient client, MessagingClient mClient, File file, String machineId, + Future sendImageMessage( + ApiClient client, + MessagingClient mClient, + File file, + String machineId, void Function(double progress) progressCallback) async { final record = await RecordApi.uploadImage( client, @@ -84,12 +90,15 @@ class _MessageInputBarState extends State { type: MessageType.object, content: jsonEncode(record.toMap()), sendTime: DateTime.now().toUtc(), - state: MessageState.local - ); + state: MessageState.local); mClient.sendMessage(message); } - Future sendVoiceMessage(ApiClient client, MessagingClient mClient, File file, String machineId, + Future sendVoiceMessage( + ApiClient client, + MessagingClient mClient, + File file, + String machineId, void Function(double progress) progressCallback) async { final record = await RecordApi.uploadVoiceClip( client, @@ -109,7 +118,11 @@ class _MessageInputBarState extends State { mClient.sendMessage(message); } - Future sendRawFileMessage(ApiClient client, MessagingClient mClient, File file, String machineId, + Future sendRawFileMessage( + ApiClient client, + MessagingClient mClient, + File file, + String machineId, void Function(double progress) progressCallback) async { final record = await RecordApi.uploadRawFile( client, @@ -133,12 +146,12 @@ class _MessageInputBarState extends State { if (!_isRecording) return; final width = MediaQuery.of(context).size.width; - if (event.localPosition.dx < width - width/4) { + if (event.localPosition.dx < width - width / 4) { if (!_recordingCancelled) { HapticFeedback.vibrate(); setState(() { - _recordingCancelled = true; - }); + _recordingCancelled = true; + }); } } else { if (_recordingCancelled) { @@ -192,17 +205,13 @@ class _MessageInputBarState extends State { _sendProgress = 0; }); final apiClient = cHolder.apiClient; - await sendVoiceMessage( - apiClient, - mClient, - file, + await sendVoiceMessage(apiClient, mClient, file, cHolder.settingsClient.currentSettings.machineId.valueOrDefault, - (progress) { - setState(() { - _sendProgress = progress; - }); - } - ); + (progress) { + setState(() { + _sendProgress = progress; + }); + }); setState(() { _isSending = false; _sendProgress = null; @@ -213,10 +222,7 @@ class _MessageInputBarState extends State { child: Container( decoration: BoxDecoration( border: const Border(top: BorderSide(width: 1, color: Colors.black)), - color: Theme - .of(context) - .colorScheme - .surfaceVariant, + color: Theme.of(context).colorScheme.surfaceVariant, ), padding: const EdgeInsets.symmetric(horizontal: 4), child: SafeArea( @@ -227,90 +233,111 @@ class _MessageInputBarState extends State { LinearProgressIndicator(value: _sendProgress), Container( decoration: BoxDecoration( - color: Theme - .of(context) - .colorScheme - .surfaceVariant, + color: Theme.of(context).colorScheme.surfaceVariant, ), child: AnimatedSwitcher( duration: const Duration(milliseconds: 200), switchInCurve: Curves.easeOut, switchOutCurve: Curves.easeOut, transitionBuilder: (Widget child, animation) => - SizeTransition(sizeFactor: animation, child: child,), - child: switch ((_attachmentPickerOpen, _loadedFiles)) { - (true, []) => - Row( - key: const ValueKey("attachment-picker"), - children: [ - TextButton.icon( - onPressed: _isSending ? null : () async { - final result = await FilePicker.platform.pickFiles( - type: FileType.image, allowMultiple: true); - if (result != null) { - setState(() { - _loadedFiles.addAll( - result.files.map((e) => - e.path != null ? (FileType.image, File(e.path!)) : null) - .whereNotNull()); - }); - } - }, - icon: const Icon(Icons.image), - label: const Text("Gallery"), - ), - TextButton.icon( - onPressed: _isSending ? null : () async { - final picture = await _imagePicker.pickImage(source: ImageSource.camera); - if (picture == null) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Failed to get image path"))); - } - return; - } - final file = File(picture.path); - if (await file.exists()) { - setState(() { - _loadedFiles.add((FileType.image, file)); - }); - } else { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Failed to load image file"))); - } - } - - }, - icon: const Icon(Icons.camera), - label: const Text("Camera"), - ), - TextButton.icon( - onPressed: _isSending ? null : () async { - final result = await FilePicker.platform.pickFiles( - type: FileType.any, allowMultiple: true); - if (result != null) { - setState(() { - _loadedFiles.addAll( - result.files.map((e) => - e.path != null ? (FileType.any, File(e.path!)) : null) - .whereNotNull()); - }); - } - }, - icon: const Icon(Icons.file_present_rounded), - label: const Text("Document"), - ), - ], - ), - (false, []) => null, - (_, _) => - MessageAttachmentList( - disabled: _isSending, - initialFiles: _loadedFiles, - onChange: (List<(FileType, File)> loadedFiles) => setState(() { - _loadedFiles.clear(); - _loadedFiles.addAll(loadedFiles); - }), + SizeTransition( + sizeFactor: animation, + child: child, ), + child: switch ((_attachmentPickerOpen, _loadedFiles)) { + (true, []) => Row( + key: const ValueKey("attachment-picker"), + children: [ + TextButton.icon( + onPressed: _isSending + ? null + : () async { + final result = await FilePicker.platform + .pickFiles( + type: FileType.image, + allowMultiple: true); + if (result != null) { + setState(() { + _loadedFiles.addAll(result.files + .map((e) => e.path != null + ? ( + FileType.image, + File(e.path!) + ) + : null) + .whereNotNull()); + }); + } + }, + icon: const Icon(Icons.image), + label: const Text("Gallery"), + ), + TextButton.icon( + onPressed: _isSending + ? null + : () async { + final picture = await _imagePicker + .pickImage(source: ImageSource.camera); + if (picture == null) { + if (context.mounted) { + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar( + content: Text( + "Failed to get image path"))); + } + return; + } + final file = File(picture.path); + if (await file.exists()) { + setState(() { + _loadedFiles + .add((FileType.image, file)); + }); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar( + content: Text( + "Failed to load image file"))); + } + } + }, + icon: const Icon(Icons.camera), + label: const Text("Camera"), + ), + TextButton.icon( + onPressed: _isSending + ? null + : () async { + final result = await FilePicker.platform + .pickFiles( + type: FileType.any, + allowMultiple: true); + if (result != null) { + setState(() { + _loadedFiles.addAll(result.files + .map((e) => e.path != null + ? (FileType.any, File(e.path!)) + : null) + .whereNotNull()); + }); + } + }, + icon: const Icon(Icons.file_present_rounded), + label: const Text("Document"), + ), + ], + ), + (false, []) => null, + (_, _) => MessageAttachmentList( + disabled: _isSending, + initialFiles: _loadedFiles, + onChange: (List<(FileType, File)> loadedFiles) => + setState(() { + _loadedFiles.clear(); + _loadedFiles.addAll(loadedFiles); + }), + ), }, ), ), @@ -318,72 +345,93 @@ class _MessageInputBarState extends State { children: [ AnimatedSwitcher( duration: const Duration(milliseconds: 200), - transitionBuilder: (Widget child, Animation animation) => - FadeTransition( - opacity: animation, - child: RotationTransition( - turns: Tween(begin: 0.6, end: 1).animate(animation), - child: child, + transitionBuilder: + (Widget child, Animation animation) => + FadeTransition( + opacity: animation, + child: RotationTransition( + turns: Tween(begin: 0.6, end: 1) + .animate(animation), + child: child, + ), + ), + child: switch ((_attachmentPickerOpen, _isRecording)) { + (_, true) => IconButton( + onPressed: () {}, + icon: Icon( + Icons.delete, + color: _recordingCancelled + ? Theme.of(context).colorScheme.error + : null, + ), + ), + (false, _) => IconButton( + key: const ValueKey("add-attachment-icon"), + onPressed: _isSending + ? null + : () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + "Sorry, this feature is not yet available"))); + return; + // setState(() { + // _attachmentPickerOpen = true; + // }); + }, + icon: const Icon( + Icons.attach_file, + ), + ), + (true, _) => IconButton( + key: const ValueKey("remove-attachment-icon"), + onPressed: _isSending + ? null + : () async { + if (_loadedFiles.isNotEmpty) { + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text( + "Remove all attachments"), + content: const Text( + "This will remove all attachments, are you sure?"), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("No"), + ), + TextButton( + onPressed: () { + setState(() { + _loadedFiles.clear(); + _attachmentPickerOpen = + false; + }); + Navigator.of(context).pop(); + }, + child: const Text("Yes"), + ) + ], + )); + } else { + setState(() { + _attachmentPickerOpen = false; + }); + } + }, + icon: const Icon( + Icons.close, ), ), - child: switch((_attachmentPickerOpen, _isRecording)) { - (_, true) => IconButton( - onPressed: () { - - }, - icon: Icon(Icons.delete, color: _recordingCancelled ? Theme.of(context).colorScheme.error : null,), - ), - (false, _) => IconButton( - key: const ValueKey("add-attachment-icon"), - onPressed: _isSending ? null : () { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Sorry, this feature is not yet available"))); - return; - setState(() { - _attachmentPickerOpen = true; - }); - }, - icon: const Icon(Icons.attach_file,), - ), - (true, _) => IconButton( - key: const ValueKey("remove-attachment-icon"), - onPressed: _isSending ? null : () async { - if (_loadedFiles.isNotEmpty) { - await showDialog(context: context, builder: (context) => - AlertDialog( - title: const Text("Remove all attachments"), - content: const Text("This will remove all attachments, are you sure?"), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("No"), - ), - TextButton( - onPressed: () { - setState(() { - _loadedFiles.clear(); - _attachmentPickerOpen = false; - }); - Navigator.of(context).pop(); - }, - child: const Text("Yes"), - ) - ], - )); - } else { - setState(() { - _attachmentPickerOpen = false; - }); - } - }, - icon: const Icon(Icons.close,), - ), }, ), Expanded( child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 4), child: Stack( children: [ TextField( @@ -404,61 +452,91 @@ class _MessageInputBarState extends State { }, style: Theme.of(context).textTheme.bodyLarge, decoration: InputDecoration( - isDense: true, - hintText: _isRecording ? "" : "Message ${widget.recipient - .username}...", - hintMaxLines: 1, - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - fillColor: Colors.black26, - filled: true, - border: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(24), - ) - ), + isDense: true, + hintText: _isRecording + ? "" + : "Message ${widget.recipient.username}...", + hintMaxLines: 1, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 12), + fillColor: Colors.black26, + filled: true, + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(24), + )), ), AnimatedSwitcher( duration: const Duration(milliseconds: 200), - transitionBuilder: (Widget child, Animation animation) => - FadeTransition( - opacity: animation, - child: SlideTransition( - position: Tween( - begin: const Offset(0, .2), - end: const Offset(0, 0), - ).animate(animation), - child: child, - ), - ), - child: _isRecording ? Padding( - padding: const EdgeInsets.symmetric(vertical: 12.0), - child: _recordingCancelled ? Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SizedBox(width: 8,), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 8.0), - child: Icon(Icons.cancel, color: Colors.red, size: 16,), - ), - Text("Cancel Recording", style: Theme.of(context).textTheme.titleMedium), - ], - ) : Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SizedBox(width: 8,), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 8.0), - child: Icon(Icons.circle, color: Colors.red, size: 16,), - ), - StreamBuilder( - stream: _recordingDurationStream(), - builder: (context, snapshot) { - return Text("Recording: ${snapshot.data?.format()}", style: Theme.of(context).textTheme.titleMedium); - } - ), - ], + transitionBuilder: + (Widget child, Animation animation) => + FadeTransition( + opacity: animation, + child: SlideTransition( + position: Tween( + begin: const Offset(0, .2), + end: const Offset(0, 0), + ).animate(animation), + child: child, ), - ) : const SizedBox.shrink(), + ), + child: _isRecording + ? Padding( + padding: const EdgeInsets.symmetric( + vertical: 12.0), + child: _recordingCancelled + ? Row( + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + const SizedBox( + width: 8, + ), + const Padding( + padding: EdgeInsets.symmetric( + horizontal: 8.0), + child: Icon( + Icons.cancel, + color: Colors.red, + size: 16, + ), + ), + Text("Cancel Recording", + style: Theme.of(context) + .textTheme + .titleMedium), + ], + ) + : Row( + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + const SizedBox( + width: 8, + ), + const Padding( + padding: EdgeInsets.symmetric( + horizontal: 8.0), + child: Icon( + Icons.circle, + color: Colors.red, + size: 16, + ), + ), + StreamBuilder( + stream: + _recordingDurationStream(), + builder: (context, snapshot) { + return Text( + "Recording: ${snapshot.data?.format()}", + style: Theme.of(context) + .textTheme + .titleMedium); + }), + ], + ), + ) + : const SizedBox.shrink(), ), ], ), @@ -466,105 +544,148 @@ class _MessageInputBarState extends State { ), AnimatedSwitcher( duration: const Duration(milliseconds: 200), - transitionBuilder: (Widget child, Animation animation) => - FadeTransition(opacity: animation, child: RotationTransition( - turns: Tween(begin: 0.5, end: 1).animate(animation), child: child,),), - child: _currentText.isNotEmpty || _loadedFiles.isNotEmpty ? IconButton( - key: const ValueKey("send-button"), - splashRadius: 24, - padding: EdgeInsets.zero, - onPressed: _isSending ? null : () async { - final cHolder = ClientHolder.of(context); - final sMsgnr = ScaffoldMessenger.of(context); - final settings = cHolder.settingsClient.currentSettings; - final toSend = List<(FileType, File)>.from(_loadedFiles); - setState(() { - _isSending = true; - _sendProgress = 0; - _attachmentPickerOpen = false; - _loadedFiles.clear(); - }); - try { - for (int i = 0; i < toSend.length; i++) { - final totalProgress = i / toSend.length; - final file = toSend[i]; - if (file.$1 == FileType.image) { - await sendImageMessage( - cHolder.apiClient, mClient, file.$2, settings.machineId.valueOrDefault, - (progress) => - setState(() { - _sendProgress = totalProgress + progress * 1 / toSend.length; - }), - ); - } else { - await sendRawFileMessage( - cHolder.apiClient, mClient, file.$2, settings.machineId.valueOrDefault, (progress) => - setState(() => - _sendProgress = totalProgress + progress * 1 / toSend.length)); - } - } - setState(() { - _sendProgress = null; - }); - - if (_currentText.isNotEmpty) { - await sendTextMessage(cHolder.apiClient, mClient, _messageTextController.text); - } - _messageTextController.clear(); - _currentText = ""; - _loadedFiles.clear(); - _attachmentPickerOpen = false; - } catch (e, s) { - FlutterError.reportError(FlutterErrorDetails(exception: e, stack: s)); - sMsgnr.showSnackBar(SnackBar(content: Text("Failed to send a message: $e"))); - } - setState(() { - _isSending = false; - _sendProgress = null; - }); - widget.onMessageSent?.call(); - }, - icon: const Icon(Icons.send), - ) : GestureDetector( - onTapUp: (_) { - _recordingCancelled = true; - }, - onTapDown: widget.disabled ? null : (_) async { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Sorry, this feature is not yet available"))); - return; - HapticFeedback.vibrate(); - final hadToAsk = await Permission.microphone.isDenied; - final hasPermission = !await _recorder.hasPermission(); - if (hasPermission) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("No permission to record audio."), - )); - } - return; - } - if (hadToAsk) { - // We had to ask for permissions so the user removed their finger from the record button. - return; - } - - final dir = await getTemporaryDirectory(); - await _recorder.start( - path: "${dir.path}/A-${const Uuid().v4()}.wav", - encoder: AudioEncoder.wav, - samplingRate: 44100 - ); - setState(() { - _isRecording = true; - }); - }, - child: IconButton( - icon: const Icon(Icons.mic_outlined), - onPressed: _isSending ? null : () { - // Empty onPressed for that sweet sweet ripple effect - }, + transitionBuilder: + (Widget child, Animation animation) => + FadeTransition( + opacity: animation, + child: RotationTransition( + turns: Tween(begin: 0.5, end: 1) + .animate(animation), + child: child, ), ), + child: _currentText.isNotEmpty || _loadedFiles.isNotEmpty + ? IconButton( + key: const ValueKey("send-button"), + splashRadius: 24, + padding: EdgeInsets.zero, + onPressed: _isSending + ? null + : () async { + final cHolder = ClientHolder.of(context); + final sMsgnr = + ScaffoldMessenger.of(context); + final settings = + cHolder.settingsClient.currentSettings; + final toSend = List<(FileType, File)>.from( + _loadedFiles); + setState(() { + _isSending = true; + _sendProgress = 0; + _attachmentPickerOpen = false; + _loadedFiles.clear(); + }); + try { + for (int i = 0; i < toSend.length; i++) { + final totalProgress = i / toSend.length; + final file = toSend[i]; + if (file.$1 == FileType.image) { + await sendImageMessage( + cHolder.apiClient, + mClient, + file.$2, + settings.machineId.valueOrDefault, + (progress) => setState(() { + _sendProgress = totalProgress + + progress * 1 / toSend.length; + }), + ); + } else { + await sendRawFileMessage( + cHolder.apiClient, + mClient, + file.$2, + settings.machineId.valueOrDefault, + (progress) => setState(() => + _sendProgress = + totalProgress + + progress * + 1 / + toSend.length)); + } + } + setState(() { + _sendProgress = null; + }); + + if (_currentText.isNotEmpty) { + await sendTextMessage( + cHolder.apiClient, + mClient, + _messageTextController.text); + } + _messageTextController.clear(); + _currentText = ""; + _loadedFiles.clear(); + _attachmentPickerOpen = false; + } catch (e, s) { + FlutterError.reportError( + FlutterErrorDetails( + exception: e, stack: s)); + sMsgnr.showSnackBar(SnackBar( + content: Text( + "Failed to send a message: $e"))); + } + setState(() { + _isSending = false; + _sendProgress = null; + }); + widget.onMessageSent?.call(); + }, + icon: const Icon(Icons.send), + ) + : GestureDetector( + onTapUp: (_) { + _recordingCancelled = true; + }, + onTapDown: widget.disabled + ? null + : (_) async { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + "Sorry, this feature is not yet available"))); + return; + // HapticFeedback.vibrate(); + // final hadToAsk = + // await Permission.microphone.isDenied; + // final hasPermission = + // !await _recorder.hasPermission(); + // if (hasPermission) { + // if (context.mounted) { + // ScaffoldMessenger.of(context) + // .showSnackBar(const SnackBar( + // content: Text( + // "No permission to record audio."), + // )); + // } + // return; + // } + // if (hadToAsk) { + // // We had to ask for permissions so the user removed their finger from the record button. + // return; + // } + + // final dir = await getTemporaryDirectory(); + // await _recorder.start( + // path: "${dir.path}/A-${const Uuid().v4()}.wav", + // const RecordConfig( + // numChannels: 1, + // sampleRate: 44100, + // encoder: AudioEncoder.wav)); + // setState(() { + // _isRecording = true; + // }); + }, + child: IconButton( + icon: const Icon(Icons.mic_outlined), + onPressed: _isSending + ? null + : () { + // Empty onPressed for that sweet sweet ripple effect + }, + ), + ), ), ], ), @@ -574,4 +695,4 @@ class _MessageInputBarState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/widgets/messages/messages_list.dart b/lib/widgets/messages/messages_list.dart index ef3590e..d9c8392 100644 --- a/lib/widgets/messages/messages_list.dart +++ b/lib/widgets/messages/messages_list.dart @@ -1,4 +1,5 @@ -import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:recon/clients/audio_cache_client.dart'; import 'package:recon/clients/messaging_client.dart'; import 'package:recon/models/users/friend.dart'; @@ -6,8 +7,6 @@ import 'package:recon/widgets/default_error_widget.dart'; import 'package:recon/widgets/friends/friend_online_status_indicator.dart'; import 'package:recon/widgets/messages/message_input_bar.dart'; import 'package:recon/widgets/messages/messages_session_header.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'message_bubble.dart'; @@ -18,7 +17,8 @@ class MessagesList extends StatefulWidget { State createState() => _MessagesListState(); } -class _MessagesListState extends State with SingleTickerProviderStateMixin { +class _MessagesListState extends State + with SingleTickerProviderStateMixin { final ScrollController _sessionListScrollController = ScrollController(); bool _showSessionListScrollChevron = false; @@ -36,7 +36,8 @@ class _MessagesListState extends State with SingleTickerProviderSt void initState() { super.initState(); _sessionListScrollController.addListener(() { - if (_sessionListScrollController.position.maxScrollExtent > 0 && !_showSessionListScrollChevron) { + if (_sessionListScrollController.position.maxScrollExtent > 0 && + !_showSessionListScrollChevron) { setState(() { _showSessionListScrollChevron = true; }); @@ -57,7 +58,9 @@ class _MessagesListState extends State with SingleTickerProviderSt return Consumer(builder: (context, mClient, _) { final friend = mClient.selectedFriend ?? Friend.empty(); final cache = mClient.getUserMessageCache(friend.id); - final sessions = friend.userStatus.decodedSessions.where((element) => element.isVisible).toList(); + final sessions = friend.userStatus.decodedSessions + .where((element) => element.isVisible) + .toList(); return Scaffold( appBar: AppBar( title: Row( @@ -74,7 +77,10 @@ class _MessagesListState extends State with SingleTickerProviderSt child: Icon( Icons.dns, size: 18, - color: Theme.of(context).colorScheme.onSecondaryContainer.withAlpha(150), + color: Theme.of(context) + .colorScheme + .onSecondaryContainer + .withAlpha(150), ), ), ], @@ -104,8 +110,8 @@ class _MessagesListState extends State with SingleTickerProviderSt if (sessions.isNotEmpty) AnimatedSwitcher( duration: const Duration(milliseconds: 200), - transitionBuilder: (child, animation) => - SizeTransition(sizeFactor: animation, axis: Axis.vertical, child: child), + transitionBuilder: (child, animation) => SizeTransition( + sizeFactor: animation, axis: Axis.vertical, child: child), child: sessions.isEmpty || !_sessionListOpen ? null : Container( @@ -133,7 +139,8 @@ class _MessagesListState extends State with SingleTickerProviderSt child: Align( alignment: Alignment.centerRight, child: Container( - padding: const EdgeInsets.only(left: 16, right: 4, top: 1, bottom: 1), + padding: const EdgeInsets.only( + left: 16, right: 4, top: 1, bottom: 1), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.centerLeft, @@ -183,11 +190,13 @@ class _MessagesListState extends State with SingleTickerProviderSt children: [ const Icon(Icons.message_outlined), Padding( - padding: const EdgeInsets.symmetric(vertical: 24), + padding: + const EdgeInsets.symmetric(vertical: 24), child: Text( "There are no messages here\nWhy not say hello?", textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleMedium, + style: + Theme.of(context).textTheme.titleMedium, ), ) ], @@ -198,7 +207,8 @@ class _MessagesListState extends State with SingleTickerProviderSt create: (BuildContext context) => AudioCacheClient(), child: ListView.builder( reverse: true, - physics: const BouncingScrollPhysics(decelerationRate: ScrollDecelerationRate.fast), + physics: const BouncingScrollPhysics( + decelerationRate: ScrollDecelerationRate.fast), itemCount: cache.messages.length, itemBuilder: (context, index) { final entry = cache.messages[index]; diff --git a/lib/widgets/messages/messages_session_header.dart b/lib/widgets/messages/messages_session_header.dart index ee3d5f6..ca9e70f 100644 --- a/lib/widgets/messages/messages_session_header.dart +++ b/lib/widgets/messages/messages_session_header.dart @@ -1,10 +1,9 @@ -import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; import 'package:recon/auxiliary.dart'; import 'package:recon/models/session.dart'; import 'package:recon/widgets/formatted_text.dart'; import 'package:recon/widgets/generic_avatar.dart'; import 'package:recon/widgets/sessions/session_view.dart'; -import 'package:flutter/material.dart'; class SessionTile extends StatelessWidget { const SessionTile({required this.session, super.key}); @@ -18,12 +17,15 @@ class SessionTile extends StatelessWidget { foregroundColor: Theme.of(context).colorScheme.onSurface, ), onPressed: () { - Navigator.of(context).push(MaterialPageRoute(builder: (context) => SessionView(session: session))); + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => SessionView(session: session))); }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - GenericAvatar(imageUri: Aux.resdbToHttp(session.thumbnailUrl), placeholderIcon: Icons.no_photography), + GenericAvatar( + imageUri: Aux.resdbToHttp(session.thumbnailUrl), + placeholderIcon: Icons.no_photography), Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: Column( @@ -33,7 +35,11 @@ class SessionTile extends StatelessWidget { FormattedText(session.formattedName), Text( "${session.sessionUsers.length.toString().padLeft(2, "0")}/${session.maxUsers.toString().padLeft(2, "0")} active users", - style: Theme.of(context).textTheme.labelMedium?.copyWith(color: Theme.of(context).colorScheme.onSurface.withOpacity(.6)), + style: Theme.of(context).textTheme.labelMedium?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurface + .withOpacity(.6)), ) ], ), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index e71e553..193930b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,12 +7,14 @@ import Foundation import audio_session import dynamic_color +import ffmpeg_kit_flutter_audio +import file_selector_macos import flutter_local_notifications import flutter_secure_storage_macos import just_audio import package_info_plus import path_provider_foundation -import record_macos +import record_darwin import share_plus import sqflite import url_launcher_macos @@ -20,12 +22,14 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) + FFmpegKitFlutterPlugin.register(with: registry.registrar(forPlugin: "FFmpegKitFlutterPlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) - FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - RecordMacosPlugin.register(with: registry.registrar(forPlugin: "RecordMacosPlugin")) + RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/macos/Podfile b/macos/Podfile index 9d3038f..28f25fa 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -41,6 +41,10 @@ post_install do |installer| flutter_additional_macos_build_settings(target) target.build_configurations.each do |config| config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '10.15' + xcconfig_path = config.base_configuration_reference.real_path + xcconfig = File.read(xcconfig_path) + xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR") + File.open(xcconfig_path, "w") { |file| file << xcconfig_mod } end end end diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 218ddee..ee6bac5 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -3,6 +3,15 @@ PODS: - FlutterMacOS - dynamic_color (0.0.2): - FlutterMacOS + - ffmpeg-kit-macos-audio (6.0) + - ffmpeg_kit_flutter_audio (6.0.3): + - ffmpeg_kit_flutter_audio/audio (= 6.0.3) + - FlutterMacOS + - ffmpeg_kit_flutter_audio/audio (6.0.3): + - ffmpeg-kit-macos-audio (= 6.0) + - FlutterMacOS + - file_selector_macos (0.0.1): + - FlutterMacOS - flutter_local_notifications (0.0.1): - FlutterMacOS - flutter_secure_storage_macos (6.1.1): @@ -18,7 +27,8 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - record_macos (0.2.0): + - record_darwin (1.0.0): + - Flutter - FlutterMacOS - share_plus (0.0.1): - FlutterMacOS @@ -31,19 +41,22 @@ PODS: DEPENDENCIES: - audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`) - dynamic_color (from `Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos`) + - ffmpeg_kit_flutter_audio (from `Flutter/ephemeral/.symlinks/plugins/ffmpeg_kit_flutter_audio/macos`) + - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - just_audio (from `Flutter/ephemeral/.symlinks/plugins/just_audio/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - - record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`) + - record_darwin (from `Flutter/ephemeral/.symlinks/plugins/record_darwin/macos`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) SPEC REPOS: trunk: + - ffmpeg-kit-macos-audio - FMDB EXTERNAL SOURCES: @@ -51,6 +64,10 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos dynamic_color: :path: Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos + ffmpeg_kit_flutter_audio: + :path: Flutter/ephemeral/.symlinks/plugins/ffmpeg_kit_flutter_audio/macos + file_selector_macos: + :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos flutter_local_notifications: :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos flutter_secure_storage_macos: @@ -63,8 +80,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin - record_macos: - :path: Flutter/ephemeral/.symlinks/plugins/record_macos/macos + record_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/record_darwin/macos share_plus: :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos sqflite: @@ -75,18 +92,21 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: audio_session: dea1f41890dbf1718f04a56f1d6150fd50039b72 dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f + ffmpeg-kit-macos-audio: d1fa3fe42922de39a494c1ac73985524f1c27131 + ffmpeg_kit_flutter_audio: db530afe9b427a980f85036618c9fe9e043a4a39 + file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9 flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4 flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a just_audio: 9b67ca7b97c61cfc9784ea23cd8cc55eb226d489 package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce - path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 - record_macos: 937889e0f2a7a12b6fc14e97a3678e5a18943de6 + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + record_darwin: 1f6619f2abac4d1ca91d3eeab038c980d76f1517 share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea - url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451 + url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 -PODFILE CHECKSUM: 4062a5d7621e35b4f677b9e411c2714a4f99d4c2 +PODFILE CHECKSUM: 3efd3b4b57928fa6a5be6b71a1f5dc6e2a2b54af COCOAPODS: 1.12.1 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index d572f3a..07ee6ce 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 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 */; }; + 3EACF4532AF95E990009EB00 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 431AA3390577660559928839 /* Pods_Runner.framework */; }; F7455EB836601EB2BB27AA8C /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6D47E6FEAF464069014E11B /* Pods_RunnerTests.framework */; }; FC9F240BEF110BF52DFF7861 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 431AA3390577660559928839 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ @@ -48,19 +49,6 @@ }; /* End PBXContainerItemProxy section */ -/* Begin PBXCopyFilesBuildPhase section */ - 33CC110E2044A8840003C045 /* Bundle Framework */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Bundle Framework"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - /* Begin PBXFileReference section */ 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 = ""; }; @@ -78,6 +66,7 @@ 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 = ""; }; + 3EACF44B2AF931920009EB00 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 431AA3390577660559928839 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 717F60D9A7608595A8BC4295 /* 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 = ""; }; @@ -103,6 +92,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3EACF4532AF95E990009EB00 /* Pods_Runner.framework in Frameworks */, FC9F240BEF110BF52DFF7861 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -175,6 +165,7 @@ 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( + 3EACF44B2AF931920009EB00 /* RunnerDebug.entitlements */, 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, @@ -237,7 +228,6 @@ 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, - 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 457DC9EDFE75FA6884827546 /* [CP] Embed Pods Frameworks */, ); @@ -474,6 +464,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; + FLUTTER_BUILD_NAME = 0.10.3; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = me.voidspace.recon.RunnerTests; @@ -489,6 +480,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; + FLUTTER_BUILD_NAME = 0.10.3; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = me.voidspace.recon.RunnerTests; @@ -504,6 +496,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; + FLUTTER_BUILD_NAME = 0.10.3; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = me.voidspace.recon.RunnerTests; @@ -544,6 +537,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + FLUTTER_BUILD_NAME = 0.10.3; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -573,6 +567,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = P9AV4LPNLL; + FLUTTER_BUILD_NAME = 0.10.3; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ReCon; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; @@ -580,7 +575,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 0.10.0; + MARKETING_VERSION = 0.10.3; PRODUCT_NAME = ReCon; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; @@ -591,6 +586,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; + FLUTTER_BUILD_NAME = 0.11.0; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; @@ -626,6 +622,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + FLUTTER_BUILD_NAME = 0.10.3; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -679,6 +676,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + FLUTTER_BUILD_NAME = 0.10.3; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -701,13 +699,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = P9AV4LPNLL; + FLUTTER_BUILD_NAME = 0.10.3; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ReCon; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; @@ -715,7 +714,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 0.10.0; + MARKETING_VERSION = 0.10.3; PRODUCT_NAME = ReCon; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -737,6 +736,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = P9AV4LPNLL; + FLUTTER_BUILD_NAME = 0.10.3; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ReCon; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; @@ -744,7 +744,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 0.10.0; + MARKETING_VERSION = 0.10.3; PRODUCT_NAME = ReCon; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; @@ -755,6 +755,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; + FLUTTER_BUILD_NAME = 0.11.0; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -763,6 +764,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; + FLUTTER_BUILD_NAME = 0.11.0; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Flutter Assemble.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Flutter Assemble.xcscheme new file mode 100644 index 0000000..44f8735 --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Flutter Assemble.xcscheme @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index f7bac07..1c305fd 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -55,11 +55,11 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" launchStyle = "0" - useCustomWorkingDirectory = "NO" + useCustomWorkingDirectory = "YES" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" - allowLocationSimulation = "YES"> + allowLocationSimulation = "NO"> com.apple.security.network.server + keychain-access-groups + + $(AppIdentifierPrefix)me.voidspace.recon + diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index afafa97..134af15 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -8,5 +8,9 @@ com.apple.security.network.client + keychain-access-groups + + $(AppIdentifierPrefix)me.voidspace.recon + diff --git a/macos/Runner/RunnerDebug.entitlements b/macos/Runner/RunnerDebug.entitlements new file mode 100644 index 0000000..427bd01 --- /dev/null +++ b/macos/Runner/RunnerDebug.entitlements @@ -0,0 +1,20 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.device.audio-input + + com.apple.security.network.client + + com.apple.security.network.server + + keychain-access-groups + + $(AppIdentifierPrefix)me.voidspace.recon + + + diff --git a/pubspec.lock b/pubspec.lock index 2cf87c9..d788584 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,6 +25,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.16" + background_downloader: + dependency: "direct main" + description: + name: background_downloader + sha256: f74abc807173daac213cd810769532c62755279936532311d994418079d16013 + url: "https://pub.dev" + source: hosted + version: "7.12.2" boolean_selector: dependency: transitive description: @@ -185,6 +193,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + ffmpeg_kit_flutter_audio: + dependency: "direct main" + description: + name: ffmpeg_kit_flutter_audio + sha256: "1e6de4d6afdd1b842dde17ef55d9cfa8911d5c4a5858e80f4371487c29e42f8a" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + ffmpeg_kit_flutter_platform_interface: + dependency: transitive + description: + name: ffmpeg_kit_flutter_platform_interface + sha256: addf046ae44e190ad0101b2fde2ad909a3cd08a2a109f6106d2f7048b7abedee + url: "https://pub.dev" + source: hosted + version: "0.2.1" file: dependency: transitive description: @@ -197,10 +221,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf" + sha256: "4e42aacde3b993c5947467ab640882c56947d9d27342a5b6f2895b23956954a6" url: "https://pub.dev" source: hosted - version: "5.3.1" + version: "6.1.1" file_selector_linux: dependency: transitive description: @@ -254,30 +278,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.1" - flutter_downloader: - dependency: "direct main" - description: - name: flutter_downloader - sha256: "4a03c705dc60b4f537796da937c80fd5bff63b175f4dd99e1539ab3ad5dbeda0" - url: "https://pub.dev" - source: hosted - version: "1.11.4" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "3.0.1" flutter_local_notifications: dependency: "direct main" description: name: flutter_local_notifications - sha256: "53c332ecee8e4d723269c1c2d0cdf7cbbff0a66cc0554d230a6f38cae81760d1" + sha256: "6d11ea777496061e583623aaf31923f93a9409ef8fcaeeefdd6cd78bf4fe5bb3" url: "https://pub.dev" source: hosted - version: "14.1.4" + version: "16.1.0" flutter_local_notifications_linux: dependency: transitive description: @@ -314,10 +330,10 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "22dbf16f23a4bcf9d35e51be1c84ad5bb6f627750565edd70dab70f3ff5fff8f" + sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "9.0.0" flutter_secure_storage_linux: dependency: transitive description: @@ -354,10 +370,10 @@ packages: dependency: transitive description: name: flutter_secure_storage_windows - sha256: "38f9501c7cb6f38961ef0e1eacacee2b2d4715c63cc83fe56449c4d3d0b47255" + sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "3.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -396,10 +412,10 @@ packages: dependency: "direct main" description: name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted - version: "0.13.6" + version: "1.1.0" http_parser: dependency: "direct main" description: @@ -412,10 +428,10 @@ packages: dependency: "direct main" description: name: image_picker - sha256: b6951e25b795d053a6ba03af5f710069c99349de9341af95155d52665cb4607c + sha256: "7d7f2768df2a8b0a3cefa5ef4f84636121987d403130e70b17ef7e2cf650ba84" url: "https://pub.dev" source: hosted - version: "0.8.9" + version: "1.0.4" image_picker_android: dependency: transitive description: @@ -516,10 +532,10 @@ packages: dependency: transitive description: name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "3.0.0" logging: dependency: "direct main" description: @@ -580,10 +596,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "10259b111176fba5c505b102e3a5b022b51dd97e30522e906d6922c745584745" + sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.2.0" package_info_plus_platform_interface: dependency: transitive description: @@ -652,18 +668,18 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5 + sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8" url: "https://pub.dev" source: hosted - version: "10.4.5" + version: "11.0.1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47" + sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e url: "https://pub.dev" source: hosted - version: "10.3.6" + version: "11.1.0" permission_handler_apple: dependency: transitive description: @@ -740,50 +756,58 @@ packages: dependency: "direct main" description: name: record - sha256: f703397f5a60d9b2b655b3acc94ba079b2d9a67dc0725bdb90ef2fee2441ebf7 + sha256: be9b710f42edf94f939dda1a1688e82a68dcd391be0a836c01e639a249f133d3 url: "https://pub.dev" source: hosted - version: "4.4.4" + version: "5.0.1" + record_android: + dependency: transitive + description: + name: record_android + sha256: "5a96286f051cf46dffd1ae7cd5f1baa82cf6a983d26389c2f8d03d03dddc711b" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + record_darwin: + dependency: transitive + description: + name: record_darwin + sha256: "78dba641ae271e555035ee68b637f7605ba9f8c60ccfd5c03b835e0b77ea201f" + url: "https://pub.dev" + source: hosted + version: "1.0.0" record_linux: dependency: transitive description: name: record_linux - sha256: "348db92c4ec1b67b1b85d791381c8c99d7c6908de141e7c9edc20dad399b15ce" + sha256: "7d0e70cd51635128fe9d37d89bafd6011d7cbba9af8dc323079ae60f23546aef" url: "https://pub.dev" source: hosted - version: "0.4.1" - record_macos: - dependency: transitive - description: - name: record_macos - sha256: d1d0199d1395f05e218207e8cacd03eb9dc9e256ddfe2cfcbbb90e8edea06057 - url: "https://pub.dev" - source: hosted - version: "0.2.2" + version: "0.7.1" record_platform_interface: dependency: transitive description: name: record_platform_interface - sha256: "7a2d4ce7ac3752505157e416e4e0d666a54b1d5d8601701b7e7e5e30bec181b4" + sha256: "3a4b56e94ecd2a0b2b43eb1fa6f94c5b8484334f5d38ef43959c4bf97fb374cf" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "1.0.2" record_web: dependency: transitive description: name: record_web - sha256: "219ffb4ca59b4338117857db56d3ffadbde3169bcaf1136f5f4d4656f4a2372d" + sha256: be8c62759b385a04dbc4ae7f5d3f78e6f0c532e72935d288aee87432bbbbb8f6 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "1.0.3" record_windows: dependency: transitive description: name: record_windows - sha256: "42d545155a26b20d74f5107648dbb3382dbbc84dc3f1adc767040359e57a1345" + sha256: "326bfbe6f5232dd773ad6b848cd94f78148f02557abff1dd4627d056b688dbdb" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "1.0.0" rxdart: dependency: transitive description: @@ -821,6 +845,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" sqflite: dependency: transitive description: @@ -977,10 +1009,10 @@ packages: dependency: "direct main" description: name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + sha256: b715b8d3858b6fa9f68f87d20d98830283628014750c2b09b6f516c1da4af2a7 url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "4.1.0" vector_math: dependency: transitive description: @@ -1009,10 +1041,10 @@ packages: dependency: transitive description: name: win32 - sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "5.0.9" workmanager: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 579c65a..88b7998 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: recon description: A Resonite Contacts App for Android # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -16,10 +16,10 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.10.3-beta+1 +version: 0.11.0-beta+1 environment: - sdk: '>=3.0.1' + sdk: ">=3.0.1" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -31,39 +31,40 @@ dependencies: flutter: sdk: flutter - http: ^0.13.5 + http: ^1.1.0 http_parser: ^4.0.2 - uuid: ^3.0.7 - flutter_secure_storage: ^8.0.0 + uuid: ^4.1.0 + flutter_secure_storage: ^9.0.0 intl: ^0.18.1 path: ^1.8.2 logging: ^1.1.1 cached_network_image: ^3.2.3 web_socket_channel: ^2.4.0 html: ^0.15.2 - just_audio: ^0.9.32 + just_audio: ^0.9.35 flutter_phoenix: ^1.1.1 url_launcher: ^6.1.10 workmanager: ^0.5.1 - flutter_local_notifications: ^14.0.0+1 + flutter_local_notifications: ^16.1.0 collection: ^1.17.0 - package_info_plus: ^3.1.2 + package_info_plus: ^4.2.0 provider: ^6.0.5 photo_view: ^0.14.0 color: ^3.0.0 dynamic_color: ^1.6.5 hive: ^2.2.3 hive_flutter: ^1.1.0 - file_picker: ^5.3.0 - record: ^4.4.4 + file_picker: ^6.1.1 + record: ^5.0.1 camera: ^0.10.5 path_provider: ^2.0.15 crypto: ^3.0.3 - image_picker: ^0.8.7+5 - permission_handler: ^10.2.0 - flutter_downloader: ^1.10.4 + image_picker: ^1.0.4 + permission_handler: ^11.0.1 flutter_cube: ^0.1.1 share_plus: ^7.1.0 + ffmpeg_kit_flutter_audio: ^6.0.3 + background_downloader: ^7.12.2 dev_dependencies: flutter_test: @@ -74,14 +75,13 @@ dev_dependencies: # 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: ^2.0.0 + flutter_lints: ^3.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 packages. 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.