diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 19124c991..a585e72a0 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -55,7 +55,7 @@ jobs: exit $INTEGRATION_TEST_EXIT_CODE - name: Upload Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: result.mkv path: commet/video.mkv diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 49b8f30e0..c3c7766bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -162,4 +162,160 @@ jobs: - name: Build Web run: | cd $PROJECT_PATH - flutter build web --release --dart-define PLATFORM=web \ No newline at end of file + flutter build web --release --dart-define PLATFORM=web + + build-mac: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: 'true' + + - name: Setup Flutter + uses: subosito/flutter-action@v2.8.0 + with: + flutter-version: '3.24.4' + channel: 'stable' + + - name: Setup Provisioning Profile + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.MACOS_SIGNING_P12 }} + P12_PASSWORD: ${{ secrets.MACOS_SIGNING_P12_PASSWORD }} + BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.COMMET_MACOS_PROVISIONPROFILE }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + # create variables + CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 + PP_PATH=$RUNNER_TEMP/build_pp.provisionprofile + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + + # import certificate and provisioning profile from secrets + echo "Making Build Certificate" + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH + echo "Making Provisioning Profile" + echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH + + # create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # import certificate to keychain + security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + # apply provisioning profile + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles/ + + - name: Create Configuration + run: | + cd $PROJECT_PATH/macos/Runner/Configs + touch AppInfo.xcconfig + echo PRODUCT_NAME=commet >> AppInfo.xcconfig + echo PRODUCT_BUNDLE_IDENTIFIER=${{ vars.MACOS_BUNDLE_ID }} >> AppInfo.xcconfig + echo PRODUCT_COPYRIGHT="Copyright © 2023 commet.chat. All rights reserved." >> AppInfo.xcconfig + echo PRODUCT_TEAM=${{ secrets.DEV_TEAM_ID }} >> AppInfo.xcconfig + echo PRODUCT_PROFILE=${{ secrets.MACOS_PROVISIONING_PROFILE_NAME }} >> AppInfo.xcconfig + + - name: Code Generation + run: | + cd $PROJECT_PATH + dart run scripts/codegen.dart + + - name: Build Mac + run: | + cd $PROJECT_PATH + flutter build macos --dart-define BUILD_MODE=release --dart-define PLATFORM=macos + + - name: Clean up keychain and provisioning profile + if: ${{ always() }} + run: | + security delete-keychain $RUNNER_TEMP/app-signing.keychain-db + rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.provisionprofile + + build-ios: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: 'true' + + - name: Setup Flutter + uses: subosito/flutter-action@v2.8.0 + with: + flutter-version: '3.24.4' + channel: 'stable' + + - name: Setup Provisioning Profile + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.IOS_SIGNING_P12 }} + P12_PASSWORD: ${{ secrets.IOS_SIGNING_P12_PASSWORD }} + BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.COMMET_IOS_MOBILEPROVISION }} + BUILD_EXTENSION_PROVISION_PROFILE_BASE64: ${{ secrets.COMMET_IOS_EXTENSION_MOBILEPROVISION }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + # create variables + CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 + PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision + PP_EXT_PATH=$RUNNER_TEMP/build_ext_pp.mobileprovision + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + + # import certificate and provisioning profile from secrets + echo "Making Build Certificate" + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH + echo "Making Provisioning Profile" + echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH + echo "Making Extension Provisioning Profile" + echo -n "$BUILD_EXTENSION_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_EXT_PATH + + # create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # import certificate to keychain + security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + # apply provisioning profile + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles/ + cp $PP_EXT_PATH ~/Library/MobileDevice/Provisioning\ Profiles/ + + - name: Create Configuration + run: | + cd $PROJECT_PATH/ios + touch AppInfo.xcconfig + echo PRODUCT_NAME=commet >> AppInfo.xcconfig + echo PRODUCT_BUNDLE_IDENTIFIER=${{ vars.IOS_BUNDLE_ID }} >> AppInfo.xcconfig + echo PRODUCT_SERVICE_BUNDLE_IDENTIFIER=${{ vars.IOS_BUNDLE_SERVICER_ID }} >> AppInfo.xcconfig + echo PRODUCT_COPYRIGHT="Copyright © 2023 commet.chat. All rights reserved." >> AppInfo.xcconfig + echo PRODUCT_TEAM=${{ secrets.DEV_TEAM_ID }} >> AppInfo.xcconfig + echo PRODUCT_PROFILE=${{ secrets.IOS_PROVISIONING_PROFILE_NAME }} >> AppInfo.xcconfig + echo PRODUCT_SERVICE_PROFILE=${{ secrets.IOS_EXTENSION_PROVISIONING_PROFILE_NAME }} >> AppInfo.xcconfig + + - name: Code Generation + run: | + cd $PROJECT_PATH + gem install plist_lite + dart run scripts/codegen.dart + cd ios + pod install + + - name: Build iOS + env: + GATEWAY: ${{ vars.IOS_PUSH_GATEWAY }} + run: | + cd $PROJECT_PATH + flutter build ipa --export-options-plist=ios/export.plist --dart-define BUILD_MODE=release --dart-define PLATFORM=ios --dart-define PUSH_GATEWAY=$GATEWAY + + - name: Clean up keychain and provisioning profile + if: ${{ always() }} + run: | + security delete-keychain $RUNNER_TEMP/app-signing.keychain-db + rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index ee7151acb..c63b4677f 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -67,7 +67,7 @@ jobs: - name: Upload Artifact if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: result.mkv path: commet/video.mkv diff --git a/README.md b/README.md index 6b5aa1047..cfdf43dfe 100644 --- a/README.md +++ b/README.md @@ -74,15 +74,15 @@ When building Commet, there are some additional command line arguments that must **Required** | **Argument** | **Valid Values** | **Description** | |--------------|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| -| PLATFORM | 'desktop', 'mobile', 'linux', 'windows', 'macos', 'android', 'ios', 'web' | Defines which platform to build for | +| PLATFORM | 'desktop', 'mobile', 'linux', 'windows', 'macos', 'android', 'ios', 'web' | Defines which platform to build for | | BUILD_MODE | 'release', 'debug' | When building with 'debug' flag, additional debug information will be shown | **Optional** -| **Argument** | **Valid Values** | **Description** | -|--------------|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| -| GIT_HASH | * | Supply the current git hash when building to show in info screen | -| VERSION_TAG | * | Supply the current build version, to display app version | -| BUILD_DETAIL | * | Can provide additional detail about the current build, for example if it was being built for Flatpak or Snap | +| **Argument** | **Valid Values** | **Description** | +|--------------|------------------|--------------------------------------------------------------------------------------------------------------| +| GIT_HASH | * | Supply the current git hash when building to show in info screen | +| VERSION_TAG | * | Supply the current build version, to display app version | +| BUILD_DETAIL | * | Can provide additional detail about the current build, for example if it was being built for Flatpak or Snap | **Example:** @@ -90,3 +90,37 @@ When building Commet, there are some additional command line arguments that must cd commet flutter run --dart-define BUILD_MODE=debug --dart-define PLATFORM=linux ``` + +## Building on macOS + +These are rough notes, based on the experience of getting Commet compiled and running for +macOS and iOS from a mac. + +- Install [Homebrew](https://brew.sh) +- Install ninja with `brew install ninja` + - Repeat for "gtk", "mpv", "ffmpeg", "libmpv", "mimalloc" +- If you want to run locally only (on a mac), you can install OLM with + `brew install libolm`. If you want to generate a universal binary, then the Commet + Xcode project for macOS includes a Cocoapods installation of "OLMKit" along with a shell + script to create an alias to the compiled library named "libolm.3.dylib", so that the + application can find it. +- For both macOS and iOS, open the Xcode projects by opening the ".xcworkspace" file + instead of the ".xcodeproj" file. + - In these files, set up code signing appropriately. You will need: + - An apple code signing certificate for iOS (and the same for macOS if you want to be + able to run the app non-locally) + - An Apple Push Services certificate for push notification support + - An Apple Identifier for your app bundle name (e.g. "chat.commet.app.WHATEVER") + - An Apple device profile for each device (mac or phone) you want to test on + - An Apple services key for push services (and your own Firebase set-up to go with it) + if you want to use push notifications outside of the main commet set-up + +The current set-up specifies the Podfile for both macOS and iOS (rather than having +Flutter generate it) because the generated Podfile both sets the wrong target OS versions +and includes a test target which is not actually present in the code. In addition, on +macOS, the pod install of 'OLMKit' *must* be done manually in the pod file because the +Flutter pod definition of OLMKit will only install it on iOS, and Homebrew will only +install the version of libolm that matches the system architecture on which it's running. +In addition, the Homebrew libolm is linked with the rest of the homebrew installation, and +thus the library may fail to work, even embedded in the app, if installed on a different +system. diff --git a/commet/ios/Flutter/AppFrameworkInfo.plist b/commet/ios/Flutter/AppFrameworkInfo.plist index 9625e105d..7c5696400 100644 --- a/commet/ios/Flutter/AppFrameworkInfo.plist +++ b/commet/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/commet/ios/Flutter/Debug.xcconfig b/commet/ios/Flutter/Debug.xcconfig index 592ceee85..ec97fc6f3 100644 --- a/commet/ios/Flutter/Debug.xcconfig +++ b/commet/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/commet/ios/Flutter/Release.xcconfig b/commet/ios/Flutter/Release.xcconfig index 592ceee85..c4855bfe2 100644 --- a/commet/ios/Flutter/Release.xcconfig +++ b/commet/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/commet/ios/Podfile b/commet/ios/Podfile new file mode 100644 index 000000000..7e6138aa8 --- /dev/null +++ b/commet/ios/Podfile @@ -0,0 +1,107 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +require 'plist_lite' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + + target.build_configurations.each do |config| + # You can remove unused permissions here + # for more information: https://github.com/Baseflow/flutter-permission-handler/blob/main/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h + # e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0' + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + '$(inherited)', + + ## dart: PermissionGroup.notification + 'PERMISSION_NOTIFICATIONS=1', + ] + + end + end + + work_dir = Dir.pwd + app_id = "" + app_profile = "" + service_id = "" + service_profile = "" + team_id = "" + env_path = "#{work_dir}/Appinfo.xcconfig" + File.foreach( env_path ) do |line| + items = line.split("=") + if ("#{items[0]}" == "PRODUCT_SERVICE_BUNDLE_IDENTIFIER") then + service_id = items[1].strip! + elsif ("#{items[0]}" == "PRODUCT_BUNDLE_IDENTIFIER") then + app_id = items[1].strip! + elsif ("#{items[0]}" == "PRODUCT_TEAM") then + team_id = items[1].strip! + elsif ("#{items[0]}" == "PRODUCT_PROFILE") then + app_profile = items[1].strip! + elsif ("#{items[0]}" == "PRODUCT_SERVICE_PROFILE") then + service_profile = items[1].strip! + end + end + Dir.glob("Pods/Target Support Files/Pods-servicer/*.xcconfig") do |xc_config_filename| + full_path_name = "#{work_dir}/#{xc_config_filename}" + if (File.exist?(full_path_name)) + new_text = "PRODUCT_BUNDLE_IDENTIFIER = #{service_id}\n" + end + File.write(full_path_name, new_text, File.size(full_path_name), mode: 'a') + end + export_path = File.expand_path(File.join('..', "export.plist"), __FILE__) + puts "Export file path is #{export_path}" + provisioning_profiles={app_id => app_profile, service_id => service_profile} + plist = { + "destination" => "export", + "manageAppVersionAndBuildNumber" => true, + "method" => "app-store-connect", + "provisioningProfiles" => provisioning_profiles, + "signingCertificate" => "Apple Distribution", + "signingStyle" => "manual", + "stripSwiftSymbols" => true, + "teamID" => team_id, + "uploadSymbols" => true + } + puts plist + plist_text = PlistLite.dump(plist) + puts plist_text + File.open(export_path, "w") {|file| file.puts PlistLite.dump(plist) } +end + +target 'servicer' do + use_frameworks! +end diff --git a/commet/ios/Runner.xcodeproj/project.pbxproj b/commet/ios/Runner.xcodeproj/project.pbxproj index c6503b75e..5cafe174d 100644 --- a/commet/ios/Runner.xcodeproj/project.pbxproj +++ b/commet/ios/Runner.xcodeproj/project.pbxproj @@ -3,19 +3,45 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 63; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 33094CB8717F20131D8A6C51 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF23A21167A1629A46938202 /* Pods_Runner.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 600CAF462D8B2821002E12F6 /* AppInfo.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 600CAF452D8B2821002E12F6 /* AppInfo.xcconfig */; }; + 600CB14A2D8F2B0A002E12F6 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 600CB1432D8F2B0A002E12F6 /* NotificationService.swift */; }; + 6021A3252D828F45001F0660 /* servicer.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 6021A31E2D828F45001F0660 /* servicer.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 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 */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + F0DC56CC087774C439052C75 /* Pods_servicer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 182DFD5CBE07ACC7F7E761BC /* Pods_servicer.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 6021A3232D828F45001F0660 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6021A31D2D828F45001F0660; + remoteInfo = servicer; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ + 6021A3262D828F45001F0660 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 6021A3252D828F45001F0660 /* servicer.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -29,9 +55,20 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0D317BE01B771C2F83770B29 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 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 = ""; }; + 182DFD5CBE07ACC7F7E761BC /* Pods_servicer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_servicer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 57C41A2591C5EDD8E3475A60 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 600CAF452D8B2821002E12F6 /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 600CB1422D8F2B0A002E12F6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 600CB1432D8F2B0A002E12F6 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 600CB1442D8F2B0A002E12F6 /* servicer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = servicer.entitlements; sourceTree = ""; }; + 600D31202D83386000F738D9 /* Flutter.xcframework */ = {isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:S8QB4VV633:FLUTTER.IO LLC"; lastKnownFileType = wrapper.xcframework; name = Flutter.xcframework; path = "../../../flutter-sdk/bin/cache/artifacts/engine/ios/extension_safe/Flutter.xcframework"; sourceTree = ""; }; + 6021A31E2D828F45001F0660 /* servicer.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = servicer.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 603701A52D728005004FE230 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 6F71ADEFB749D97791F21965 /* Pods-servicer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-servicer.debug.xcconfig"; path = "Target Support Files/Pods-servicer/Pods-servicer.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -42,19 +79,52 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + ABF91C8D62261D1B58A78884 /* Pods-servicer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-servicer.release.xcconfig"; path = "Target Support Files/Pods-servicer/Pods-servicer.release.xcconfig"; sourceTree = ""; }; + D0E55B5F3A40F501ACD4023C /* Pods-servicer.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-servicer.profile.xcconfig"; path = "Target Support Files/Pods-servicer/Pods-servicer.profile.xcconfig"; sourceTree = ""; }; + DD5B2EEE2F68770E413E08AF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + DF23A21167A1629A46938202 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 6021A31B2D828F45001F0660 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F0DC56CC087774C439052C75 /* Pods_servicer.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 33094CB8717F20131D8A6C51 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5F731FA9B1EF6A5DF5DC817A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 600D31202D83386000F738D9 /* Flutter.xcframework */, + DF23A21167A1629A46938202 /* Pods_Runner.framework */, + 182DFD5CBE07ACC7F7E761BC /* Pods_servicer.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 600CB1452D8F2B0A002E12F6 /* servicer */ = { + isa = PBXGroup; + children = ( + 600CB1422D8F2B0A002E12F6 /* Info.plist */, + 600CB1432D8F2B0A002E12F6 /* NotificationService.swift */, + 600CB1442D8F2B0A002E12F6 /* servicer.entitlements */, + ); + path = servicer; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -71,7 +141,11 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, + 600CB1452D8F2B0A002E12F6 /* servicer */, 97C146EF1CF9000F007C117D /* Products */, + AAF1D69EC431FC127C369128 /* Pods */, + 5F731FA9B1EF6A5DF5DC817A /* Frameworks */, + 600CAF452D8B2821002E12F6 /* AppInfo.xcconfig */, ); sourceTree = ""; }; @@ -79,6 +153,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 6021A31E2D828F45001F0660 /* servicer.appex */, ); name = Products; sourceTree = ""; @@ -86,6 +161,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 603701A52D728005004FE230 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -98,23 +174,59 @@ path = Runner; sourceTree = ""; }; + AAF1D69EC431FC127C369128 /* Pods */ = { + isa = PBXGroup; + children = ( + 0D317BE01B771C2F83770B29 /* Pods-Runner.debug.xcconfig */, + DD5B2EEE2F68770E413E08AF /* Pods-Runner.release.xcconfig */, + 57C41A2591C5EDD8E3475A60 /* Pods-Runner.profile.xcconfig */, + 6F71ADEFB749D97791F21965 /* Pods-servicer.debug.xcconfig */, + ABF91C8D62261D1B58A78884 /* Pods-servicer.release.xcconfig */, + D0E55B5F3A40F501ACD4023C /* Pods-servicer.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 6021A31D2D828F45001F0660 /* servicer */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6021A32B2D828F45001F0660 /* Build configuration list for PBXNativeTarget "servicer" */; + buildPhases = ( + 0B5A74AC87AAFB82FC770C06 /* [CP] Check Pods Manifest.lock */, + 6021A31A2D828F45001F0660 /* Sources */, + 6021A31B2D828F45001F0660 /* Frameworks */, + 6021A31C2D828F45001F0660 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = servicer; + productName = servicer; + productReference = 6021A31E2D828F45001F0660 /* servicer.appex */; + productType = "com.apple.product-type.app-extension"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + A06759ECA63285C3A4F0528E /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, + 6021A3262D828F45001F0660 /* Embed Foundation Extensions */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + B7544AEBA57AB2081ADCD564 /* [CP] Embed Pods Frameworks */, + 006FB0C14E09DB927049DA3C /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( + 6021A3242D828F45001F0660 /* PBXTargetDependency */, ); name = Runner; productName = Runner; @@ -127,9 +239,13 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastSwiftUpdateCheck = 1620; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 6021A31D2D828F45001F0660 = { + CreatedOnToolsVersion = 16.2; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -137,7 +253,7 @@ }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; + compatibilityVersion = "Xcode 15.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -150,11 +266,20 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 6021A31D2D828F45001F0660 /* servicer */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 6021A31C2D828F45001F0660 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 600CAF462D8B2821002E12F6 /* AppInfo.xcconfig in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -169,6 +294,45 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 006FB0C14E09DB927049DA3C /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 0B5A74AC87AAFB82FC770C06 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-servicer-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -176,6 +340,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -199,9 +364,56 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + A06759ECA63285C3A4F0528E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B7544AEBA57AB2081ADCD564 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 6021A31A2D828F45001F0660 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 600CB14A2D8F2B0A002E12F6 /* NotificationService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -213,6 +425,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 6021A3242D828F45001F0660 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6021A31D2D828F45001F0660 /* servicer */; + targetProxy = 6021A3232D828F45001F0660 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -235,6 +455,7 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 600CAF452D8B2821002E12F6 /* AppInfo.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -274,7 +495,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -288,24 +509,167 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = J6B4LYQ6NB; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = chat.commet.commetapp; + PRODUCT_BUNDLE_IDENTIFIER = chat.commet.commetapp.quirt; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = commet_ios_distribution; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; + 6021A3272D828F45001F0660 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6F71ADEFB749D97791F21965 /* Pods-servicer.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = servicer/servicer.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = J6B4LYQ6NB; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = servicer/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = servicer; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = commet_ios_extension_distribution; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6021A3282D828F45001F0660 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ABF91C8D62261D1B58A78884 /* Pods-servicer.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = servicer/servicer.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = J6B4LYQ6NB; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = servicer/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = servicer; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = commet_ios_extension_distribution; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 6021A3292D828F45001F0660 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D0E55B5F3A40F501ACD4023C /* Pods-servicer.profile.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = servicer/servicer.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = J6B4LYQ6NB; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = servicer/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = servicer; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = commet_ios_extension_distribution; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 600CAF452D8B2821002E12F6 /* AppInfo.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -351,7 +715,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -361,6 +725,7 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 600CAF452D8B2821002E12F6 /* AppInfo.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -400,7 +765,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -416,16 +781,26 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = J6B4LYQ6NB; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = chat.commet.commetapp; + PRODUCT_BUNDLE_IDENTIFIER = "--PRODUCT-BUNDLE-IDENTIFIER-"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = commet_ios_distribution; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -438,16 +813,26 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = J6B4LYQ6NB; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = chat.commet.commetapp; + PRODUCT_BUNDLE_IDENTIFIER = chat.commet.commetapp.quirt; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = commet_ios_distribution; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -457,6 +842,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 6021A32B2D828F45001F0660 /* Build configuration list for PBXNativeTarget "servicer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6021A3272D828F45001F0660 /* Debug */, + 6021A3282D828F45001F0660 /* Release */, + 6021A3292D828F45001F0660 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/commet/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/commet/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a33..5e31d3d34 100644 --- a/commet/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/commet/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + diff --git a/commet/ios/Runner/AppDelegate.swift b/commet/ios/Runner/AppDelegate.swift index 70693e4a8..f0e7131ee 100644 --- a/commet/ios/Runner/AppDelegate.swift +++ b/commet/ios/Runner/AppDelegate.swift @@ -1,13 +1,105 @@ import UIKit import Flutter +import UserNotifications -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { + + let channelName : String = "PushNotificationChannel" + var deviceToken : String = "" + override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + let controller : FlutterViewController = window?.rootViewController as! FlutterViewController + let pushNotificationChannel = FlutterMethodChannel(name: channelName, binaryMessenger: controller.binaryMessenger) + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + } + + pushNotificationChannel.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) in + switch call.method { + case "requestNotificationPermissions": + self?.requestNotificationPermissions(result: result) + case "registerForPushNotifications": + self?.registerForPushNotifications(application: application, result: result) + case "retrieveDeviceToken": + self?.getDeviceToken(result: result) + default: + result(FlutterMethodNotImplemented) + } + } + GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + let deviceTokenString = deviceToken.reduce("", { $0 + String(format: "%02X", $1) }) + let controller: FlutterViewController = window?.rootViewController as! FlutterViewController + let pushNotificationChannel = FlutterMethodChannel(name: channelName, binaryMessenger: controller.binaryMessenger) + self.deviceToken = deviceTokenString + pushNotificationChannel.invokeMethod("didRegister", arguments: deviceTokenString) + } + + override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + } + + + + private func requestNotificationPermissions(result: @escaping FlutterResult) { + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in + if let error = error { + result(FlutterError(code: "PERMISSION_ERROR", message: "Failed to request permissions", details: error.localizedDescription)) + return + } + result(granted) + } + } + + private func registerForPushNotifications(application: UIApplication, result: @escaping FlutterResult) { + application.registerForRemoteNotifications() + result("Device Token registration initiated") + } + + private func getDeviceToken(result: @escaping FlutterResult) { + if(deviceToken.isEmpty) { + result(FlutterError(code: "UNAVAILABLE", message: "Device token not available", details: nil)) + } else { + result(deviceToken) + } + } + + override func application(_ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable: Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + handleNotification(methodName: "onBackgroundNotification", userInfo: userInfo) + completionHandler(UIBackgroundFetchResult.noData) + } + + override func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + if #available(iOS 14.0, *) { + completionHandler([.banner, .list, .sound, .badge]) + } else { + completionHandler([.alert, .sound, .badge]) + } + } + + override func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { + let userInfo = response.notification.request.content.userInfo + handleNotification(methodName: "onPushNotification", userInfo: userInfo) + completionHandler() + } + + private func handleNotification(methodName: String, userInfo: [AnyHashable: Any]) { + let controller: FlutterViewController = window?.rootViewController as! FlutterViewController + let pushNotificationChannel = FlutterMethodChannel(name: channelName, binaryMessenger: controller.binaryMessenger) + pushNotificationChannel.invokeMethod(methodName, arguments: userInfo) } } diff --git a/commet/ios/Runner/Info.plist b/commet/ios/Runner/Info.plist index 0fb038ed1..e701f920a 100644 --- a/commet/ios/Runner/Info.plist +++ b/commet/ios/Runner/Info.plist @@ -2,6 +2,16 @@ + BGTaskScheduler + + dev.flutter.background.refresh + + BGTaskSchedulerPermittedIdentifiers + + dev.flutter.background.refresh + + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -22,8 +32,22 @@ ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) + FirebaseAppDelegateProxyEnabled + + ITSAppUsesNonExemptEncryption + LSRequiresIPhoneOS + NSPhotoLibraryUsageDescription + Commet requests access to your photo library in order to allow you to share photos in your Matrix servers and rooms. + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + fetch + remote-notification + processing + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -43,9 +67,5 @@ UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/commet/ios/Runner/Runner.entitlements b/commet/ios/Runner/Runner.entitlements new file mode 100644 index 000000000..fb5eb5be7 --- /dev/null +++ b/commet/ios/Runner/Runner.entitlements @@ -0,0 +1,12 @@ + + + + + aps-environment + development + com.apple.security.application-groups + + group.commet.ios + + + diff --git a/commet/ios/servicer/Info.plist b/commet/ios/servicer/Info.plist new file mode 100644 index 000000000..57421ebf9 --- /dev/null +++ b/commet/ios/servicer/Info.plist @@ -0,0 +1,13 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/commet/ios/servicer/NotificationService.swift b/commet/ios/servicer/NotificationService.swift new file mode 100644 index 000000000..f1ed58445 --- /dev/null +++ b/commet/ios/servicer/NotificationService.swift @@ -0,0 +1,48 @@ +// +// NotificationService.swift +// servicer +// +// Created by Brian Quirt on 3/12/25. +// + +import UserNotifications + +class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + + if let bestAttemptContent = bestAttemptContent { + var displayRoom : String = "" + let roomId = bestAttemptContent.userInfo["room_id"] as! String + if roomId.contains(":") { + let roomCode = roomId.lastIndex(of:":")! + displayRoom = String(roomId[roomCode...]) + } else { + displayRoom = roomId + } + let unreadCount = bestAttemptContent.userInfo["unread_count"] as! Int + + bestAttemptContent.title = "\(displayRoom)" + bestAttemptContent.subtitle = "\(unreadCount) new message(s)" + bestAttemptContent.body = "Tap on this message to be taken to the room where it happened™️" + + contentHandler(bestAttemptContent) + } + } + + override func serviceExtensionTimeWillExpire() { + // Called just before the extension will be terminated by the system. + // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { + bestAttemptContent.title = "New Matrix Message" + bestAttemptContent.body = "Tap on this message to be taken to the room where it happened™️" + contentHandler(bestAttemptContent) + } + } + +} diff --git a/commet/ios/servicer/servicer.entitlements b/commet/ios/servicer/servicer.entitlements new file mode 100644 index 000000000..476981528 --- /dev/null +++ b/commet/ios/servicer/servicer.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.commet.ios + + + diff --git a/commet/lib/client/components/push_notification/android/firebase_push_notifier.dart b/commet/lib/client/components/push_notification/firebase_push_notifier.dart similarity index 99% rename from commet/lib/client/components/push_notification/android/firebase_push_notifier.dart rename to commet/lib/client/components/push_notification/firebase_push_notifier.dart index 75e6f5bf9..3fb07f929 100644 --- a/commet/lib/client/components/push_notification/android/firebase_push_notifier.dart +++ b/commet/lib/client/components/push_notification/firebase_push_notifier.dart @@ -65,7 +65,7 @@ Future _firebaseMessagingBackgroundHandler(dynamic message) async { } class FirebasePushNotifier implements Notifier { - late AndroidNotifier notifier; + late Notifier notifier; @override bool get hasPermission => notifier.hasPermission; diff --git a/commet/lib/client/components/push_notification/ios/ios_mutable_notifier.dart b/commet/lib/client/components/push_notification/ios/ios_mutable_notifier.dart new file mode 100644 index 000000000..e40b6503f --- /dev/null +++ b/commet/lib/client/components/push_notification/ios/ios_mutable_notifier.dart @@ -0,0 +1,75 @@ +import 'dart:async'; + +import 'package:commet/cache/file_cache.dart'; +import 'package:commet/client/client_manager.dart'; +import 'package:commet/client/member.dart'; +import 'package:commet/client/timeline_events/timeline_event_encrypted.dart'; +import 'package:commet/debug/log.dart'; +import 'package:commet/main.dart'; + +class IOSNotificationMutator { + Completer completer; + + IOSNotificationMutator(this.completer); + + Future init() async { + isHeadless = true; + + if (fileCache == null) { + fileCache = FileCache.getFileCacheInstance(); + + if (fileCache != null) { + await fileCache?.init(); + } + } + + shortcutsManager.init(); + + clientManager = await ClientManager.init(isBackgroundService: true); + } + + Future> handleMessage(Map? data) async { + if (data == null) { + completer.complete(); + return { + "title": "New Message", + "subtitle": "", + "body": "Received Message" + }; + } + + var roomId = data["room_id"] as String; + var eventId = data["event_id"] as String; + + var client = + clientManager!.clients.firstWhere((element) => element.hasRoom(roomId)); + + Log.i("Found client: ${client.identifier}"); + var room = client.getRoom(roomId); + Log.i("Found room: ${room?.displayName}"); + + var event = await room!.getEvent(eventId); + Member? user = await room.fetchMember(event!.senderId); + + Log.i("Got user: $user ($user)"); + + if (event is TimelineEventEncrypted) { + var decrypted = await event.attemptDecrypt(room); + event = decrypted ?? event; + } + + var content = { + "title": user.displayName, + "subtitle": room.displayName, + "body": event.plainTextBody + }; + completer.complete(); + return content; + } + + void channelHandler({channel}) async { + channel.setMethodCallHandler((call) async { + handleMessage(call.arguments); + }); + } +} diff --git a/commet/lib/client/components/push_notification/ios/ios_notifier.dart b/commet/lib/client/components/push_notification/ios/ios_notifier.dart new file mode 100644 index 000000000..2dd39a7ad --- /dev/null +++ b/commet/lib/client/components/push_notification/ios/ios_notifier.dart @@ -0,0 +1,163 @@ +import 'dart:convert'; + +import 'package:commet/client/components/push_notification/notification_content.dart'; +import 'package:commet/client/components/push_notification/notifier.dart'; +import 'package:commet/client/components/push_notification/push_notification_component.dart'; +import 'package:commet/client/room.dart'; +import 'package:commet/config/build_config.dart'; +import 'package:commet/debug/log.dart'; +import 'package:commet/main.dart'; +import 'package:commet/utils/event_bus.dart'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:convert/convert.dart'; + +class IOSNotifier implements Notifier { + static const String _channelName = "PushNotificationChannel"; + static const MethodChannel _channel = MethodChannel(_channelName); + + static String? deviceToken; + static String? hexDeviceTokenUpper; + static String? hexDeviceTokenLower; + static String? base64DeviceToken; + static String pushGateway = ""; + static const bool deviceTokenIsHex = false; + + @override + bool hasPermission = false; + + @override + bool get needsToken => true; + + @override + bool get enabled => true; + + @override + Future init() async { + if (BuildConfig.PUSH_GATEWAY == "binocs") { + pushGateway = "https://sygnal.spacebinoculars.matrix.town"; + } else if (BuildConfig.PUSH_GATEWAY != "unset") { + pushGateway = BuildConfig.PUSH_GATEWAY; + } else { + pushGateway = "push.commet.chat"; + } + preferences.setPushGateway(pushGateway); + await requestPermission().then((value) async { + await registerDevice().then((_) async {}); + }); + } + + @override + Future notify(NotificationContent notification) async {} + + static Future requestPushNotificationPermission() async { + try { + await _channel.invokeMethod("requestNotificationPermissions"); + } on PlatformException catch (e) { + Log.e("Failed to get permission with message $e.message"); + throw PlatformException(message: e.message, code: e.code); + } + } + + @override + Future requestPermission() async { + try { + await IOSNotifier.requestPushNotificationPermission(); + return true; + } on PlatformException catch (e) { + Log.e("Error Getting Permission: $e.message"); + return false; + } + } + + static Future registerDevice() async { + try { + await _channel.invokeMethod("registerForPushNotifications"); + } on PlatformException { + return; + } + } + + static Future retriveDeviceToken() async { + try { + String? token = + await _channel.invokeMethod("retrieveDeviceToken"); + return token; + } on PlatformException catch (e) { + Log.e("Error on token retrieval: $e.message"); + throw PlatformException(message: e.message, code: e.code); + } + } + + @override + Future getToken() async { + try { + String? token = await IOSNotifier.retriveDeviceToken(); + if (deviceTokenIsHex) { + return token!.toLowerCase(); + } + return base64.encode(hex.decode(token!)); + } on PlatformException { + Log.i("Failed to get token, device token is $deviceToken"); + if (deviceTokenIsHex) { + return hexDeviceTokenLower; + } + return base64DeviceToken; + } + } + + @override + Map? extraRegistrationData() { + var extraData = { + "default_payload": { + "aps": { + "mutable-content": 1, + "content-available": 1, + "alert": {"loc-key": "SINGLE_UNREAD", "loc-args": []} + }, + "hex_device_token": hexDeviceTokenLower, + }, + }; + return extraData; + } + + static handlerPushNotificationData({required BuildContext context}) async { + _channel.setMethodCallHandler((call) async { + if (call.method == "onPushNotification") { + Log.i("in onPushNotification"); + final eventId = call.arguments['event_id']; + final roomId = call.arguments['room_id']; + + if (eventId == null || roomId == null) { + return; + } + + var client = clientManager!.clients + .firstWhere((element) => element.hasRoom(roomId)); + + EventBus.openRoom.add((roomId, client.identifier)); + } else if (call.method == "didRegister") { + Log.i("Registration Finished"); + hexDeviceTokenUpper = call.arguments as String; + base64DeviceToken = base64.encode(hex.decode(hexDeviceTokenUpper!)); + hexDeviceTokenLower = hexDeviceTokenUpper!.toLowerCase(); + if (deviceTokenIsHex) { + deviceToken = hexDeviceTokenLower; + } else { + deviceToken = base64DeviceToken; + } + await PushNotificationComponent.updateAllPushers(); + } else if (call.method == "onBackgroundNotification") { + Log.i("in onBackgroundNotification()"); + var callArguments = call.arguments; + Log.i("Message Arguments: $callArguments"); + } + }); + } + + @override + Future clearNotifications(Room room) async { + return; + } +} diff --git a/commet/lib/client/components/push_notification/macos/macos_notifier.dart b/commet/lib/client/components/push_notification/macos/macos_notifier.dart new file mode 100644 index 000000000..c50f77c7b --- /dev/null +++ b/commet/lib/client/components/push_notification/macos/macos_notifier.dart @@ -0,0 +1,161 @@ +import 'dart:convert'; + +import 'package:commet/client/components/push_notification/notification_content.dart'; +import 'package:commet/client/components/push_notification/notifier.dart'; +import 'package:commet/client/components/push_notification/push_notification_component.dart'; +import 'package:commet/client/room.dart'; +import 'package:commet/debug/log.dart'; +import 'package:commet/main.dart'; +import 'package:commet/utils/event_bus.dart'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:convert/convert.dart'; + +class MacosNotifier implements Notifier { + static const String _channelName = "PushNotificationChannel"; + static const MethodChannel _channel = MethodChannel(_channelName); + + static String? deviceToken; + static String? hexDeviceTokenUpper; + static String? hexDeviceTokenLower; + static String? base64DeviceToken; + static const String pushGateway = + "https://sygnal.spacebinoculars.matrix.town"; + static const bool deviceTokenIsHex = false; + + @override + bool hasPermission = false; + + @override + bool get needsToken => true; + + @override + bool get enabled => true; + + @override + Future init() async { + preferences.setPushGateway(pushGateway); + await requestPermission().then((value) async { + await registerDevice().then((_) async {}); + }); + } + + @override + Future notify(NotificationContent notification) async {} + + static Future requestPushNotificationPermission() async { + try { + await _channel.invokeMethod("requestNotificationPermissions"); + } on PlatformException catch (e) { + Log.e("Failed to get permission with message $e.message"); + throw PlatformException(message: e.message, code: e.code); + } + } + + @override + Future requestPermission() async { + try { + await MacosNotifier.requestPushNotificationPermission(); + return true; + } on PlatformException catch (e) { + Log.e("Error Getting Permission: $e.message"); + return false; + } + } + + static Future registerDevice() async { + try { + await _channel.invokeMethod("registerForPushNotifications"); + } on PlatformException { + return; + } + } + + static Future retriveDeviceToken() async { + try { + String? token = + await _channel.invokeMethod("retrieveDeviceToken"); + return token; + } on PlatformException catch (e) { + Log.e("Error on token retrieval: $e.message"); + throw PlatformException(message: e.message, code: e.code); + } + } + + @override + Future getToken() async { + try { + String? token = await MacosNotifier.retriveDeviceToken(); + if (deviceTokenIsHex) { + return token!.toLowerCase(); + } + return base64.encode(hex.decode(token!)); + } on PlatformException { + Log.i("Failed to get token, device token is $deviceToken"); + if (deviceTokenIsHex) { + return hexDeviceTokenLower; + } + return base64DeviceToken; + } + } + + @override + Map? extraRegistrationData() { + var extraData = { + "default_payload": { + "aps": { + "mutable-content": 1, + "content-available": 1, + "alert": {"loc-key": "SINGLE_UNREAD", "loc-args": []} + }, + "hex_device_token": hexDeviceTokenLower, + }, + }; + return extraData; + } + + static handlerPushNotificationData({required BuildContext context}) async { + _channel.setMethodCallHandler((call) async { + var callMethod = call.method; + Log.i("Channel Called with method $callMethod"); + if (call.method == "onPushNotification") { + Log.i("in onPushNotification"); + final eventId = call.arguments['event_id']; + final roomId = call.arguments['room_id']; + + if (eventId == null || roomId == null) { + return; + } + + var client = clientManager!.clients + .firstWhere((element) => element.hasRoom(roomId)); + + EventBus.openRoom.add((roomId, client.identifier)); + } else if (call.method == "didRegister") { + Log.i("Registered"); + hexDeviceTokenUpper = call.arguments as String; + Log.i("Received token $hexDeviceTokenUpper"); + base64DeviceToken = base64.encode(hex.decode(hexDeviceTokenUpper!)); + Log.i("Base64-encoded token is $base64DeviceToken"); + hexDeviceTokenLower = hexDeviceTokenUpper!.toLowerCase(); + Log.i("Lowercase Token is $hexDeviceTokenLower"); + if (deviceTokenIsHex) { + deviceToken = hexDeviceTokenLower; + } else { + deviceToken = base64DeviceToken; + } + await PushNotificationComponent.updateAllPushers(); + } else if (call.method == "onBackgroundNotification") { + Log.i("in onBackgroundNotification()"); + var callArguments = call.arguments; + Log.i("Message Arguments: $callArguments"); + } + }); + } + + @override + Future clearNotifications(Room room) async { + return; + } +} diff --git a/commet/lib/client/components/push_notification/notification_content.dart b/commet/lib/client/components/push_notification/notification_content.dart index f6e3a12b2..30182cc4c 100644 --- a/commet/lib/client/components/push_notification/notification_content.dart +++ b/commet/lib/client/components/push_notification/notification_content.dart @@ -20,6 +20,7 @@ class MessageNotificationContent extends NotificationContent { String roomId; String clientId; String roomName; + String? source; bool isDirectMessage; ImageProvider? roomImage; ImageProvider? senderImage; @@ -37,5 +38,6 @@ class MessageNotificationContent extends NotificationContent { this.roomImage, this.senderImage, this.attachedImage, + this.source, }) : super(title: senderName, content: content); } diff --git a/commet/lib/client/components/push_notification/notification_manager.dart b/commet/lib/client/components/push_notification/notification_manager.dart index 641a429d7..73c16f8a9 100644 --- a/commet/lib/client/components/push_notification/notification_manager.dart +++ b/commet/lib/client/components/push_notification/notification_manager.dart @@ -1,9 +1,11 @@ import 'package:commet/client/alert.dart'; import 'package:commet/client/client.dart'; +import 'package:commet/client/components/push_notification/firebase_push_notifier.dart'; import 'package:commet/client/components/push_notification/android/android_notifier.dart'; -import 'package:commet/client/components/push_notification/android/firebase_push_notifier.dart'; +import 'package:commet/client/components/push_notification/ios/ios_notifier.dart'; import 'package:commet/client/components/push_notification/android/unified_push_notifier.dart'; import 'package:commet/client/components/push_notification/linux/linux_notifier.dart'; +import 'package:commet/client/components/push_notification/macos/macos_notifier.dart'; import 'package:commet/client/components/push_notification/modifiers/notification_modifiers.dart'; import 'package:commet/client/components/push_notification/modifiers/suppress_active_room.dart'; import 'package:commet/client/components/push_notification/modifiers/suppress_other_device_active.dart'; @@ -80,6 +82,14 @@ class NotificationManager { } } + if (PlatformUtils.isIOS) { + return IOSNotifier(); + } + + if (PlatformUtils.isMacOS) { + return MacosNotifier(); + } + return null; } diff --git a/commet/lib/client/matrix/components/push_notifications/matrix_push_notification_component.dart b/commet/lib/client/matrix/components/push_notifications/matrix_push_notification_component.dart index 54515d5dd..1449404a6 100644 --- a/commet/lib/client/matrix/components/push_notifications/matrix_push_notification_component.dart +++ b/commet/lib/client/matrix/components/push_notifications/matrix_push_notification_component.dart @@ -2,6 +2,7 @@ import 'package:commet/client/components/push_notification/notification_manager. import 'package:commet/client/components/push_notification/push_notification_component.dart'; import 'package:commet/client/matrix/matrix_client.dart'; import 'package:commet/config/build_config.dart'; +import 'package:commet/config/platform_utils.dart'; import 'package:commet/debug/log.dart'; import 'package:commet/main.dart'; import 'package:matrix/matrix.dart'; @@ -19,8 +20,6 @@ class MatrixPushNotificationComponent {Map? extraData}) async { var matrixClient = client.getMatrixClient(); - Log.i("Current push key: $pushKey"); - var pushers = await matrixClient.getPushers(); if (pushers != null && @@ -28,8 +27,10 @@ class MatrixPushNotificationComponent return; } + var appId = PlatformUtils.appID; + var pusher = Pusher( - appId: "chat.commet.commetapp.android", + appId: appId, pushkey: pushKey, appDisplayName: BuildConfig.appName, data: PusherData( @@ -52,9 +53,18 @@ class MatrixPushNotificationComponent // Check for stale pushers if (pushers != null) { for (var pusher in pushers) { - if (pusher.deviceDisplayName == deviceName && - (pusher.pushkey != currentPushKey || - pusher.data.url != pushGateway)) { + if (pusher.appId == PlatformUtils.appID && + ((pusher.deviceDisplayName != deviceName && + pusher.pushkey == currentPushKey) || + (pusher.deviceDisplayName == deviceName && + (pusher.pushkey != currentPushKey || + pusher.data.url != pushGateway)))) { + // 2 cases here: + // - existing pusher with the same key (i.e. same device for + // receiving notifications) but different device name, so + // a change of some sort has happened. + // - Same name but different key or different gateway + // But always we only want to remove the commet pushers. await matrixClient.deletePusher(pusher); } } @@ -72,6 +82,10 @@ class MatrixPushNotificationComponent var extraData = notifier?.extraRegistrationData(); var name = mxClient.clientName; + if (PlatformUtils.isIOS) { + name = "iPhone"; + } + var uri = Uri.parse(preferences.pushGateway); if (uri.hasScheme == false) { uri = Uri.https(preferences.pushGateway); @@ -86,9 +100,11 @@ class MatrixPushNotificationComponent await cleanOldPushers(key, name, uri); if (key == null) { + Log.w("Device key is null"); return; } + Log.i("Registering pusher"); await ensurePushNotificationsRegistered(key, uri, name, extraData: extraData); } diff --git a/commet/lib/client/matrix/matrix_room.dart b/commet/lib/client/matrix/matrix_room.dart index 1c28fe4c6..92f80e1cf 100644 --- a/commet/lib/client/matrix/matrix_room.dart +++ b/commet/lib/client/matrix/matrix_room.dart @@ -225,7 +225,7 @@ class MatrixRoom extends Room { } // let push notifications handle it - if (BuildConfig.ANDROID) { + if (BuildConfig.ANDROID || BuildConfig.IOS) { return; } diff --git a/commet/lib/config/build_config.dart b/commet/lib/config/build_config.dart index ea2fdad30..d22ed7209 100644 --- a/commet/lib/config/build_config.dart +++ b/commet/lib/config/build_config.dart @@ -26,6 +26,9 @@ class BuildConfig { static const bool RELEASE = _buildMode == _Constants.release; + static const String PUSH_GATEWAY = + String.fromEnvironment('PUSH_GATEWAY', defaultValue: "unset"); + static const bool DESKTOP = PLATFORM == _Constants._desktop || PLATFORM == _Constants._linux || PLATFORM == _Constants._windows || @@ -91,4 +94,4 @@ class _Constants { static const String _mobile = "mobile"; static const String _web = "web"; -} +} \ No newline at end of file diff --git a/commet/lib/config/platform_utils.dart b/commet/lib/config/platform_utils.dart index bb788cf53..245af6bc9 100644 --- a/commet/lib/config/platform_utils.dart +++ b/commet/lib/config/platform_utils.dart @@ -22,4 +22,25 @@ class PlatformUtils { static bool get isWeb { return kIsWeb; } + + static bool get isIOS { + if (kIsWeb) return false; + return Platform.isIOS; + } + + static bool get isMacOS { + if (kIsWeb) return false; + return Platform.isMacOS; + } + + static String get appID { + if (Platform.isIOS) { + return "chat.commet.commetapp.quirt"; + } else if (Platform.isMacOS) { + return "chat.commet.commetapp.macos"; + } else if (Platform.isAndroid) { + return "chat.commet.commetapp.android"; + } + return "chat.commet.commetapp"; + } } diff --git a/commet/lib/main.dart b/commet/lib/main.dart index 0ed6424b4..c93fba86e 100644 --- a/commet/lib/main.dart +++ b/commet/lib/main.dart @@ -1,9 +1,13 @@ import 'dart:async'; +import 'dart:ui'; import 'package:commet/cache/file_cache.dart'; import 'package:commet/client/client_manager.dart'; import 'package:commet/client/components/component.dart'; import 'package:commet/client/components/push_notification/notification_manager.dart'; +import 'package:commet/client/components/push_notification/ios/ios_notifier.dart'; +import 'package:commet/client/components/push_notification/ios/ios_mutable_notifier.dart'; +import 'package:commet/client/components/push_notification/macos/macos_notifier.dart'; import 'package:commet/config/build_config.dart'; import 'package:commet/config/global_config.dart'; import 'package:commet/config/layout_config.dart'; @@ -39,6 +43,7 @@ import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:tiamat/config/style/theme_changer.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:tiamat/config/style/theme_dark.dart'; +import 'package:flutter_background_service/flutter_background_service.dart'; final GlobalKey navigator = GlobalKey(); FileCache? fileCache; @@ -96,6 +101,30 @@ void bubble() async { ))); } +@pragma('vm:entry-point') +void notificationMutationService() async { + DartPluginRegistrant.ensureInitialized(); + Log.prefix = "notification-mutation-service"; + Log.i("Starting notification mutation service"); + final Completer completer = Completer(); + if (!preferences.isInit) { + await preferences.init(); + } + Log.i("Preferences initialized"); + + const String channelName = "PushServiceChannel"; + const MethodChannel channel = MethodChannel(channelName); + + var notificationMutator = IOSNotificationMutator(completer); + await notificationMutator.init(); + Log.i("Initialized mutator class"); + + notificationMutator.channelHandler(channel: channel); + Log.i("listening"); + await completer.future; + Log.i("done"); +} + void main() async { runZonedGuarded(appMain, Log.onError, zoneSpecification: Log.spec); } @@ -254,6 +283,11 @@ class App extends StatelessWidget { @override Widget build(BuildContext context) { + if (PlatformUtils.isIOS) { + IOSNotifier.handlerPushNotificationData(context: context); + } else if (PlatformUtils.isMacOS) { + MacosNotifier.handlerPushNotificationData(context: context); + } return ThemeChanger( shouldFollowSystemTheme: () => preferences.shouldFollowSystemTheme, getDarkTheme: () { diff --git a/commet/lib/ui/atoms/dismiss_keyboard.dart b/commet/lib/ui/atoms/dismiss_keyboard.dart new file mode 100644 index 000000000..131b94825 --- /dev/null +++ b/commet/lib/ui/atoms/dismiss_keyboard.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +class DismissKeyboard extends StatelessWidget { + final Widget child; + const DismissKeyboard({Key? key, required this.child}) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus && + currentFocus.focusedChild != null) { + FocusManager.instance.primaryFocus?.unfocus(); + } + }, + child: child, + ); + } +} diff --git a/commet/lib/ui/atoms/keyboard_adaptor.dart b/commet/lib/ui/atoms/keyboard_adaptor.dart index a47ac9f16..1c9169827 100644 --- a/commet/lib/ui/atoms/keyboard_adaptor.dart +++ b/commet/lib/ui/atoms/keyboard_adaptor.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:commet/ui/atoms/dismiss_keyboard.dart'; import 'package:commet/ui/atoms/scaled_safe_area.dart'; import 'package:commet/utils/scaled_app.dart'; import 'package:flutter/material.dart'; @@ -14,9 +15,10 @@ class KeyboardAdaptor extends StatelessWidget { var scaledQuery = MediaQuery.of(context).scale(); var offset = max(scaledQuery.viewInsets.bottom, scaledQuery.padding.bottom); - return ScaledSafeArea( - bottom: false, - child: Padding( - padding: EdgeInsets.fromLTRB(0, 0, 0, offset), child: child)); + return DismissKeyboard( + child: ScaledSafeArea( + bottom: false, + child: Padding( + padding: EdgeInsets.fromLTRB(0, 0, 0, offset), child: child))); } } diff --git a/commet/lib/ui/molecules/message_input.dart b/commet/lib/ui/molecules/message_input.dart index 40c951020..b5f4adb57 100644 --- a/commet/lib/ui/molecules/message_input.dart +++ b/commet/lib/ui/molecules/message_input.dart @@ -315,6 +315,15 @@ class MessageInputState extends State { } if (HardwareKeyboard.instance.isShiftPressed) { + String text = controller.text; + TextSelection textSelection = controller.selection; + String newText = + text.replaceRange(textSelection.start, textSelection.end, "\n"); + controller.text = newText; + controller.selection = textSelection.copyWith( + baseOffset: textSelection.start + 1, + extentOffset: textSelection.start + 1, + ); return KeyEventResult.ignored; } diff --git a/commet/lib/ui/pages/settings/categories/app/settings_category_app.dart b/commet/lib/ui/pages/settings/categories/app/settings_category_app.dart index 90ef17a8e..9f89acda3 100644 --- a/commet/lib/ui/pages/settings/categories/app/settings_category_app.dart +++ b/commet/lib/ui/pages/settings/categories/app/settings_category_app.dart @@ -66,7 +66,7 @@ class SettingsCategoryApp implements SettingsCategory { return const WindowSettingsPage(); }), // We really only need to configure on unified push - if (BuildConfig.ANDROID && + if ((BuildConfig.ANDROID || BuildConfig.IOS) && (!BuildConfig.ENABLE_GOOGLE_SERVICES || preferences.developerMode)) SettingsTab( label: labelSettingsAppNotifications, diff --git a/commet/lib/ui/pages/settings/categories/developer/developer_settings_page.dart b/commet/lib/ui/pages/settings/categories/developer/developer_settings_page.dart index 191bffe8c..d6cc0236c 100644 --- a/commet/lib/ui/pages/settings/categories/developer/developer_settings_page.dart +++ b/commet/lib/ui/pages/settings/categories/developer/developer_settings_page.dart @@ -222,7 +222,30 @@ class _DeveloperSettingsPageState extends State { )); }, ), - ]) + ]), + Wrap(spacing: 8, runSpacing: 8, children: [ + tiamat.Button( + text: "Delayed Message Notification", + onTap: () async { + var client = clientManager!.clients.first; + var room = client.rooms.first; + var user = client.self!; + Future.delayed(const Duration(milliseconds: 5000), () { + NotificationManager.notify(MessageNotificationContent( + senderName: user.displayName, + senderImage: user.avatar, + senderId: user.identifier, + roomName: room.displayName, + roomId: room.identifier, + content: "Test Message!", + clientId: client.identifier, + eventId: "fake_event_id", + isDirectMessage: true, + )); + }); + }, + ), + ]), ], ); } diff --git a/commet/macos/Flutter/Flutter-Debug.xcconfig b/commet/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b60..4b81f9b2d 100644 --- a/commet/macos/Flutter/Flutter-Debug.xcconfig +++ b/commet/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/commet/macos/Flutter/Flutter-Release.xcconfig b/commet/macos/Flutter/Flutter-Release.xcconfig index c2efd0b60..5caa9d157 100644 --- a/commet/macos/Flutter/Flutter-Release.xcconfig +++ b/commet/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/commet/macos/Flutter/GeneratedPluginRegistrant.swift b/commet/macos/Flutter/GeneratedPluginRegistrant.swift deleted file mode 100644 index 213f26a95..000000000 --- a/commet/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// Generated file. Do not edit. -// - -import FlutterMacOS -import Foundation - -import desktop_drop -import device_info_plus -import dynamic_color -import flutter_local_notifications -import flutter_web_auth_2 -import media_kit_libs_macos_video -import media_kit_video -import package_info_plus -import pasteboard -import path_provider_foundation -import screen_brightness_macos -import screen_retriever -import shared_preferences_foundation -import sqflite_darwin -import sqlite3_flutter_libs -import url_launcher_macos -import wakelock_plus -import window_manager -import window_to_front - -func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) - DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) - DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) - FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) - FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin")) - MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) - MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) - FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) - PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin")) - PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) - ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) - SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) - SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) - Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) - UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) - WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) - WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin")) -} diff --git a/commet/macos/Podfile b/commet/macos/Podfile new file mode 100644 index 000000000..98fa9cc12 --- /dev/null +++ b/commet/macos/Podfile @@ -0,0 +1,41 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + pod "OLMKit" +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/commet/macos/Runner.xcodeproj/project.pbxproj b/commet/macos/Runner.xcodeproj/project.pbxproj index 83c675b80..739092232 100644 --- a/commet/macos/Runner.xcodeproj/project.pbxproj +++ b/commet/macos/Runner.xcodeproj/project.pbxproj @@ -26,6 +26,8 @@ 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 */; }; + 60367AC52D723B8600EA0A89 /* OLMKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60367AC42D723B8600EA0A89 /* OLMKit.framework */; }; + AC4192BA0488492D25D9E4A3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89C5F16CEF9F24CA598260E4 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -38,23 +40,13 @@ }; /* 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 */ + 15B0A6FC1C7284C778A19AA3 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 15E6C7095F45D47F4D59CBD9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 293A9321CED3782F58B1C21B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* commet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "commet.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* .app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = .app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -66,7 +58,10 @@ 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 = ""; }; + 60367AC42D723B8600EA0A89 /* OLMKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = OLMKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 60E898A32D72337F00696429 /* OLMKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = OLMKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 89C5F16CEF9F24CA598260E4 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ @@ -75,6 +70,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 60367AC52D723B8600EA0A89 /* OLMKit.framework in Frameworks */, + AC4192BA0488492D25D9E4A3 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -99,13 +96,14 @@ 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 98839A8BA3B2B6F0EC91A84C /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* commet.app */, + 33CC10ED2044A3C60003C045 /* .app */, ); name = Products; sourceTree = ""; @@ -145,9 +143,22 @@ path = Runner; sourceTree = ""; }; + 98839A8BA3B2B6F0EC91A84C /* Pods */ = { + isa = PBXGroup; + children = ( + 15B0A6FC1C7284C778A19AA3 /* Pods-Runner.debug.xcconfig */, + 15E6C7095F45D47F4D59CBD9 /* Pods-Runner.release.xcconfig */, + 293A9321CED3782F58B1C21B /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 60367AC42D723B8600EA0A89 /* OLMKit.framework */, + 89C5F16CEF9F24CA598260E4 /* Pods_Runner.framework */, + 60E898A32D72337F00696429 /* OLMKit.framework */, ); name = Frameworks; sourceTree = ""; @@ -159,11 +170,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 4A3EC4A4E39DC40045050BEE /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, - 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 11C171EF4F711973987131AF /* [CP] Embed Pods Frameworks */, + 6063DE702D7B737B00E5E1D0 /* ShellScript */, ); buildRules = ( ); @@ -172,7 +185,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* commet.app */; + productReference = 33CC10ED2044A3C60003C045 /* .app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -182,13 +195,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; @@ -233,6 +245,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 11C171EF4F711973987131AF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -269,7 +298,47 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire\n"; + }; + 4A3EC4A4E39DC40045050BEE /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 6063DE702D7B737B00E5E1D0 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "framedir=${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}\nln -s \"./OLMKit\" $framedir/OLMKit.framework/libolm.3.dylib\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -309,7 +378,7 @@ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -360,20 +429,33 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = J6B4LYQ6NB; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + "LD_RUNPATH_SEARCH_PATHS[arch=*]" = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../Frameworks/OLMKit.framework", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = commet_macos_profile; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -486,14 +568,26 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = J6B4LYQ6NB; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + "LD_RUNPATH_SEARCH_PATHS[arch=*]" = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../Frameworks/OLMKit.framework", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = commet_macos_profile; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; @@ -506,20 +600,33 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = J6B4LYQ6NB; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + "LD_RUNPATH_SEARCH_PATHS[arch=*]" = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../Frameworks/OLMKit.framework", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = commet_macos_profile; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -528,6 +635,7 @@ }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/commet/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner Release.xcscheme b/commet/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner Release.xcscheme new file mode 100644 index 000000000..10f772855 --- /dev/null +++ b/commet/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner Release.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/commet/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/commet/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index b0f960503..992df92a1 100644 --- a/commet/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/commet/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + diff --git a/commet/macos/Runner/AppDelegate.swift b/commet/macos/Runner/AppDelegate.swift index d53ef6437..d0da9135b 100644 --- a/commet/macos/Runner/AppDelegate.swift +++ b/commet/macos/Runner/AppDelegate.swift @@ -1,9 +1,97 @@ import Cocoa import FlutterMacOS +import OSLog +import UserNotifications -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { - override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } + let channelName : String = "PushNotificationChannel" + var deviceToken : String = "" + + + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationDidFinishLaunching(_ aNotification: Notification) { + let controller : FlutterViewController = mainFlutterWindow?.contentViewController as! FlutterViewController + let pushNotificationChannel : FlutterMethodChannel = FlutterMethodChannel.init(name: self.channelName, binaryMessenger: controller.engine.binaryMessenger) + + UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + + pushNotificationChannel.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) in + switch call.method { + case "requestNotificationPermissions": + self?.requestNotificationPermissions(result: result) + case "registerForPushNotifications": + self?.registerForPushNotifications(application: aNotification.object as! NSApplication, result: result) + case "retrieveDeviceToken": + self?.getDeviceToken(result: result) + default: + result(FlutterMethodNotImplemented) + } + } + + super.applicationDidFinishLaunching(aNotification) + } + + override func application(_ application: NSApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + let tokenParts = deviceToken.map { data -> String in + return String(format: "%02.2hhx", data) + } + let token = tokenParts.joined() + let controller : FlutterViewController = mainFlutterWindow?.contentViewController as! FlutterViewController + let pushNotificationChannel = FlutterMethodChannel(name: channelName, binaryMessenger: controller.engine.binaryMessenger) + self.deviceToken = token + + let logger = Logger() + logger.notice("Registered for remote notifications with token: \(tokenParts)") + + pushNotificationChannel.invokeMethod("didRegister", arguments: token) + } + + private func requestNotificationPermissions(result: @escaping FlutterResult) { + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in + if let error = error { + result(FlutterError(code: "PERMISSION_ERROR", message: "Failed to request permissions", details: error.localizedDescription)) + return + } + result(granted) + } + } + + private func registerForPushNotifications(application: NSApplication, result: @escaping FlutterResult) { + application.registerForRemoteNotifications() + result("Device Token registration initiated") + } + + private func getDeviceToken(result: @escaping FlutterResult) { + if(deviceToken.isEmpty) { + result(FlutterError(code: "UNAVAILABLE", message: "Device token not available", details: nil)) + } else { + result(deviceToken) + } + } + + override func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) { + let controller : FlutterViewController = mainFlutterWindow?.contentViewController as! FlutterViewController + let pushNotificationChannel = FlutterMethodChannel(name: channelName, binaryMessenger: controller.engine.binaryMessenger) + pushNotificationChannel.invokeMethod("onBackgroundNotification", arguments: userInfo) + } + + func usernotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + if #available(macOS 11.0, *) { + completionHandler([.banner, .list, .badge, .sound]) + } else { + completionHandler([.alert, .badge, .sound]) + } + + } + + func usernotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + let controller : FlutterViewController = mainFlutterWindow?.contentViewController as! FlutterViewController + let pushNotificationChannel = FlutterMethodChannel(name: channelName, binaryMessenger: controller.engine.binaryMessenger) + pushNotificationChannel.invokeMethod("onPushNotification", arguments: response.notification.request.content.userInfo) + completionHandler() + } } diff --git a/commet/macos/Runner/Configs/AppInfo.xcconfig b/commet/macos/Runner/Configs/AppInfo.xcconfig deleted file mode 100644 index 95b7695cf..000000000 --- a/commet/macos/Runner/Configs/AppInfo.xcconfig +++ /dev/null @@ -1,14 +0,0 @@ -// Application-level settings for the Runner target. -// -// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the -// future. If not, the values below would default to using the project name when this becomes a -// 'flutter create' template. - -// The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = commet - -// The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = chat.commet.commetapp - -// The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2023 commet.chat. All rights reserved. diff --git a/commet/macos/Runner/DebugProfile.entitlements b/commet/macos/Runner/DebugProfile.entitlements index dddb8a30c..b76b509ee 100644 --- a/commet/macos/Runner/DebugProfile.entitlements +++ b/commet/macos/Runner/DebugProfile.entitlements @@ -2,10 +2,14 @@ + com.apple.developer.aps-environment + development com.apple.security.app-sandbox com.apple.security.cs.allow-jit + com.apple.security.network.client + com.apple.security.network.server diff --git a/commet/macos/Runner/MainFlutterWindow.swift b/commet/macos/Runner/MainFlutterWindow.swift index 2722837ec..844cdbaca 100644 --- a/commet/macos/Runner/MainFlutterWindow.swift +++ b/commet/macos/Runner/MainFlutterWindow.swift @@ -2,14 +2,16 @@ import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) - RegisterGeneratedPlugins(registry: flutterViewController) + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } - super.awakeFromNib() - } } diff --git a/commet/macos/Runner/Release.entitlements b/commet/macos/Runner/Release.entitlements index 852fa1a47..39bde1896 100644 --- a/commet/macos/Runner/Release.entitlements +++ b/commet/macos/Runner/Release.entitlements @@ -2,7 +2,11 @@ + com.apple.developer.aps-environment + development com.apple.security.app-sandbox + com.apple.security.network.client + diff --git a/commet/pubspec.lock b/commet/pubspec.lock index 022770e9e..81542de7b 100644 --- a/commet/pubspec.lock +++ b/commet/pubspec.lock @@ -223,7 +223,7 @@ packages: source: hosted version: "4.1.0" convert: - dependency: transitive + dependency: "direct main" description: name: convert sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 @@ -415,6 +415,38 @@ packages: url: "https://pub.dev" source: hosted version: "5.5.0" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.dev" + source: hosted + version: "0.9.3+4" fixnum: dependency: transitive description: @@ -737,6 +769,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.3.0" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "82652a75e3dd667a91187769a6a2cc81bd8c111bbead698d8e938d2b63e5e89a" + url: "https://pub.dev" + source: hosted + version: "0.8.12+21" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + url: "https://pub.dev" + source: hosted + version: "0.8.12+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" implicitly_animated_list: dependency: "direct main" description: diff --git a/commet/pubspec.yaml b/commet/pubspec.yaml index 56e2400d2..74e0698d5 100644 --- a/commet/pubspec.yaml +++ b/commet/pubspec.yaml @@ -90,6 +90,8 @@ dependencies: exif: ^3.3.0 image: ^4.2.0 reflectable: ^4.0.10 + convert: ^3.0.0 + image_picker: ^1.1.2 ## ---- Putting some extra lines in here to help git with the diff ## ---- Probably good to keep this stuff at the bottom of the list diff --git a/commet/scripts/apply_google_services.patch b/commet/scripts/apply_google_services.patch index 3801541bc..0a715c6d6 100644 --- a/commet/scripts/apply_google_services.patch +++ b/commet/scripts/apply_google_services.patch @@ -24,10 +24,10 @@ index 7b03921..a059ce6 100644 } } -diff --git a/commet/lib/client/components/push_notification/android/firebase_push_notifier.dart b/commet/lib/client/components/push_notification/android/firebase_push_notifier.dart +diff --git a/commet/lib/client/components/push_notification/firebase_push_notifier.dart b/commet/lib/client/components/push_notification/firebase_push_notifier.dart index 67d69a7..beb0791 100644 ---- a/commet/lib/client/components/push_notification/android/firebase_push_notifier.dart -+++ b/commet/lib/client/components/push_notification/android/firebase_push_notifier.dart +--- a/commet/lib/client/components/push_notification/firebase_push_notifier.dart ++++ b/commet/lib/client/components/push_notification/firebase_push_notifier.dart @@ -12,10 +12,10 @@ import 'package:commet/service/background_service.dart'; import 'package:commet/service/background_service_notifications/background_service_task_notification.dart';