Import IKAR messenger client and server

This commit is contained in:
sevenhill
2026-10-07 22:06:07 +03:00
commit 69ea5091eb
1951 changed files with 424546 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
</dict>
</plist>
+2
View File
@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
+2
View File
@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
+165
View File
@@ -0,0 +1,165 @@
# 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 'json'
# Firebase is Android-only: FCM is used solely by the `oneme` flavor, and on
# iOS there is a single `ru.qlyra.app` bundle that never initialises push.
# Keep the Firebase SDK out of the iOS build entirely so iOS is not forced up
# to the iOS 15 deployment target that firebase_core's podspec requires.
FIREBASE_IOS_PLUGINS = %w[firebase_core firebase_messaging].freeze
deps_file = File.expand_path('../.flutter-plugins-dependencies', __dir__)
if File.exist?(deps_file)
deps = JSON.parse(File.read(deps_file))
ios_plugins = deps.dig('plugins', 'ios')
if ios_plugins
ios_plugins.reject! { |p| FIREBASE_IOS_PLUGINS.include?(p['name']) }
File.write(deps_file, JSON.generate(deps))
end
end
registrant = File.expand_path('Runner/GeneratedPluginRegistrant.m', __dir__)
if File.exist?(registrant)
src = File.read(registrant)
FIREBASE_IOS_PLUGINS.each do |mod|
cls = "FLT#{mod.split('_').map(&:capitalize).join}Plugin"
m = Regexp.escape(mod)
src = src.gsub(/#if __has_include\(<#{m}\/[^>]+>\).*?#endif\n/m, '')
src = src.gsub(/^\s*#import <#{m}\/[^>]+>\n/, '')
src = src.gsub(/^\s*@import #{m};\n/, '')
src = src.gsub(/^\s*\[#{cls} registerWithRegistrar:.*\n/, '')
end
File.write(registrant, src)
end
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!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
# libopus ships with ogg_opus_player as a static xcframework. Nothing in the app
# references its encoder entry points, so the linker would drop them and
# `DynamicLibrary.process()` (OpusOggEncoder) would find nothing. Force-loading
# the slice keeps the whole library, which is what voice-message encoding needs.
#
# The slice is referenced in the plugin source tree rather than through
# `PODS_XCFRAMEWORKS_BUILD_DIR`: with `use_frameworks!` CocoaPods copies vendored
# xcframeworks into the pod target's own build dir, so for Runner that variable
# expands to a file nothing ever produces and Xcode fails on the missing input.
OPUS_XCFRAMEWORK = 'ogg_opus_player/darwin/Frameworks/libopus.xcframework'.freeze
OPUS_LIB_VAR = 'QLYRA_OPUS_LIB'.freeze
OPUS_FORCE_LOAD = %(-force_load "$(#{OPUS_LIB_VAR})").freeze
OPUS_XCFRAMEWORK_DIR =
File.expand_path(File.join('.symlinks', 'plugins', OPUS_XCFRAMEWORK), __dir__).freeze
def opus_slice(simulator)
slice = Dir.glob(File.join(OPUS_XCFRAMEWORK_DIR, 'ios-*')).find do |dir|
name = File.basename(dir)
next false if name.include?('maccatalyst')
next false unless File.exist?(File.join(dir, 'libopus.a'))
name.end_with?('-simulator') == simulator
end
kind = simulator ? 'simulator' : 'device'
raise "libopus.xcframework has no ios #{kind} slice in #{OPUS_XCFRAMEWORK_DIR}" if slice.nil?
File.basename(slice)
end
# permission_handler compiles every permission handler unless told otherwise;
# the unused ones reference APIs that make App Store review ask for usage
# descriptions the app has no reason to declare.
PERMISSION_MACROS = %w[
PERMISSION_CAMERA=1
PERMISSION_MICROPHONE=1
PERMISSION_PHOTOS=1
PERMISSION_PHOTOS_ADD_ONLY=1
PERMISSION_LOCATION=1
PERMISSION_LOCATION_WHENINUSE=1
PERMISSION_CONTACTS=1
PERMISSION_NOTIFICATIONS=1
PERMISSION_LOCATION_ALWAYS=0
PERMISSION_MEDIA_LIBRARY=0
PERMISSION_EVENTS=0
PERMISSION_EVENTS_FULL_ACCESS=0
PERMISSION_REMINDERS=0
PERMISSION_SPEECH_RECOGNIZER=0
PERMISSION_SENSORS=0
PERMISSION_BLUETOOTH=0
PERMISSION_APP_TRACKING_TRANSPARENCY=0
PERMISSION_CRITICAL_ALERTS=0
PERMISSION_ASSISTANT=0
].freeze
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
next unless target.name == 'permission_handler_apple'
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] =
['$(inherited)'] + PERMISSION_MACROS
end
end
opus_xcframework = "$(SRCROOT)/.symlinks/plugins/#{OPUS_XCFRAMEWORK}"
opus_device_lib = "#{opus_xcframework}/#{opus_slice(false)}/libopus.a"
opus_simulator_lib = "#{opus_xcframework}/#{opus_slice(true)}/libopus.a"
installer.aggregate_targets.each do |target|
next unless target.name == 'Pods-Runner'
%w[debug profile release].each do |name|
xcconfig = target.xcconfig_path(name)
next unless File.exist?(xcconfig)
contents = File.read(xcconfig)
next if contents.include?(OPUS_LIB_VAR)
contents += "\n" unless contents.end_with?("\n")
contents += "#{OPUS_LIB_VAR}[sdk=iphoneos*] = #{opus_device_lib}\n"
contents += "#{OPUS_LIB_VAR}[sdk=iphonesimulator*] = #{opus_simulator_lib}\n"
if contents =~ /^OTHER_LDFLAGS = .*$/
contents = contents.sub(/^OTHER_LDFLAGS = (.*)$/) do
"OTHER_LDFLAGS = #{Regexp.last_match(1)} #{OPUS_FORCE_LOAD}"
end
else
contents += "OTHER_LDFLAGS = $(inherited) #{OPUS_FORCE_LOAD}\n"
end
File.write(xcconfig, contents)
end
end
end
+209
View File
@@ -0,0 +1,209 @@
PODS:
- app_links (7.0.0):
- Flutter
- device_info_plus (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.9):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.9)
- DKImagePickerController/PhotoGallery (4.3.9):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.9)
- DKPhotoGallery (0.0.19):
- DKPhotoGallery/Core (= 0.0.19)
- DKPhotoGallery/Model (= 0.0.19)
- DKPhotoGallery/Preview (= 0.0.19)
- DKPhotoGallery/Resource (= 0.0.19)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.19):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.19):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- flutter_inappwebview_ios (0.0.1):
- Flutter
- flutter_inappwebview_ios/Core (= 0.0.1)
- OrderedSet (~> 6.0.3)
- flutter_inappwebview_ios/Core (0.0.1):
- Flutter
- OrderedSet (~> 6.0.3)
- flutter_local_notifications (0.0.1):
- Flutter
- flutter_secure_storage_darwin (10.0.0):
- Flutter
- FlutterMacOS
- flutter_timezone (0.0.1):
- Flutter
- flutter_webrtc (1.4.0):
- Flutter
- WebRTC-SDK (= 144.7559.09)
- geolocator_apple (1.2.0):
- Flutter
- FlutterMacOS
- media_kit_video (0.0.1):
- Flutter
- mobile_scanner (7.0.0):
- Flutter
- FlutterMacOS
- ogg_opus_player (0.0.1):
- Flutter
- FlutterMacOS
- open_filex (0.0.2):
- Flutter
- OrderedSet (6.0.3)
- package_info_plus (0.4.5):
- Flutter
- photo_manager (3.9.0):
- Flutter
- FlutterMacOS
- record_ios (1.2.1):
- Flutter
- SDWebImage (5.21.7):
- SDWebImage/Core (= 5.21.7)
- SDWebImage/Core (5.21.7)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- SwiftyGif (5.4.5)
- url_launcher_ios (0.0.1):
- Flutter
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
- wakelock_plus (0.0.1):
- Flutter
- WebRTC-SDK (144.7559.09)
DEPENDENCIES:
- app_links (from `.symlinks/plugins/app_links/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
- flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`)
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
- ogg_opus_player (from `.symlinks/plugins/ogg_opus_player/darwin`)
- open_filex (from `.symlinks/plugins/open_filex/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- photo_manager (from `.symlinks/plugins/photo_manager/darwin`)
- record_ios (from `.symlinks/plugins/record_ios/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- OrderedSet
- SDWebImage
- SwiftyGif
- WebRTC-SDK
EXTERNAL SOURCES:
app_links:
:path: ".symlinks/plugins/app_links/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_inappwebview_ios:
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_secure_storage_darwin:
:path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
flutter_timezone:
:path: ".symlinks/plugins/flutter_timezone/ios"
flutter_webrtc:
:path: ".symlinks/plugins/flutter_webrtc/ios"
geolocator_apple:
:path: ".symlinks/plugins/geolocator_apple/darwin"
media_kit_video:
:path: ".symlinks/plugins/media_kit_video/ios"
mobile_scanner:
:path: ".symlinks/plugins/mobile_scanner/darwin"
ogg_opus_player:
:path: ".symlinks/plugins/ogg_opus_player/darwin"
open_filex:
:path: ".symlinks/plugins/open_filex/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
photo_manager:
:path: ".symlinks/plugins/photo_manager/darwin"
record_ios:
:path: ".symlinks/plugins/record_ios/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
flutter_local_notifications: 643a3eda1ce1c0599413ca31672536d423dee214
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
flutter_webrtc: 3ee86aa10e0313c38c93fdb215dd86e77014f96a
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
media_kit_video: f3b0d035d89def15cfbbcf7dc2ae278f201e2f83
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
ogg_opus_player: 954784304f4e2722780018c4abf284e4f93cddf5
open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
photo_manager: 25fd77df14f4f0ba5ef99e2c61814dde77e2bceb
record_ios: 980fd386a97a35987d0fce3dfda4b26a38c90f4c
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
video_player_avfoundation: 3453f792138786248960ca029747fcd9f318ef52
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
WebRTC-SDK: e6006119cd730d6315d875e4a421b6cc8bb88833
PODFILE CHECKSUM: 527e8cb59c3d722cc71f78b05ab13f192b9335d3
COCOAPODS: 1.16.2
+779
View File
@@ -0,0 +1,779 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
4B1A4BBCD56B3CE42AD0A480 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 789A1AEF2F4FF7DEED202476 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
AA11BB22CC33DD44EE550101 /* QlyraVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11BB22CC33DD44EE550001 /* QlyraVideo.swift */; };
AA11BB22CC33DD44EE550102 /* QlyraVideoNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11BB22CC33DD44EE550002 /* QlyraVideoNote.swift */; };
AA11BB22CC33DD44EE550103 /* QlyraNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11BB22CC33DD44EE550003 /* QlyraNotifications.swift */; };
AA11BB22CC33DD44EE550105 /* QlyraClipboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11BB22CC33DD44EE550005 /* QlyraClipboard.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 */; };
B1B2C3D41234567890ABCDEF /* MinimalIcon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D41234567890ABCDEF /* MinimalIcon@2x.png */; };
B1B2C3D41234567890ABCDF0 /* MinimalIcon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D41234567890ABCDF0 /* MinimalIcon@3x.png */; };
E835CC948F2F948DC3BBF405 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AE8CB2654009FE276CACA4B /* Pods_RunnerTests.framework */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
1615A82960D98F780416FDE7 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
28C4A8DBB53D5AC72DDB74A4 /* 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 = "<group>"; };
2AE8CB2654009FE276CACA4B /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
366C243BBAFE3F87FBDF57A1 /* 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 = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
AA11BB22CC33DD44EE550001 /* QlyraVideo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QlyraVideo.swift; sourceTree = "<group>"; };
AA11BB22CC33DD44EE550002 /* QlyraVideoNote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QlyraVideoNote.swift; sourceTree = "<group>"; };
AA11BB22CC33DD44EE550003 /* QlyraNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QlyraNotifications.swift; sourceTree = "<group>"; };
AA11BB22CC33DD44EE550005 /* QlyraClipboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QlyraClipboard.swift; sourceTree = "<group>"; };
AA11BB22CC33DD44EE550004 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
782E66FE4E292DCFD0D1B7D2 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
789A1AEF2F4FF7DEED202476 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
83CE6DC2BACBB6F59AEA57B6 /* 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 = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
A1B2C3D41234567890ABCDEF /* MinimalIcon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MinimalIcon@2x.png"; sourceTree = "<group>"; };
A1B2C3D41234567890ABCDF0 /* MinimalIcon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MinimalIcon@3x.png"; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9D7B0FFE63E0BD5D67D7758D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
2594AD7A0161CA1A66191F05 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
E835CC948F2F948DC3BBF405 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
4B1A4BBCD56B3CE42AD0A480 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
3E066142FD515067AADAC58D /* Frameworks */ = {
isa = PBXGroup;
children = (
789A1AEF2F4FF7DEED202476 /* Pods_Runner.framework */,
2AE8CB2654009FE276CACA4B /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
C4C44F130EEB07A77FD05026 /* Pods */,
3E066142FD515067AADAC58D /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
A1B2C3D41234567890ABCDEF /* MinimalIcon@2x.png */,
A1B2C3D41234567890ABCDF0 /* MinimalIcon@3x.png */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
AA11BB22CC33DD44EE550001 /* QlyraVideo.swift */,
AA11BB22CC33DD44EE550002 /* QlyraVideoNote.swift */,
AA11BB22CC33DD44EE550003 /* QlyraNotifications.swift */,
AA11BB22CC33DD44EE550005 /* QlyraClipboard.swift */,
AA11BB22CC33DD44EE550004 /* Runner.entitlements */,
);
path = Runner;
sourceTree = "<group>";
};
C4C44F130EEB07A77FD05026 /* Pods */ = {
isa = PBXGroup;
children = (
83CE6DC2BACBB6F59AEA57B6 /* Pods-Runner.debug.xcconfig */,
366C243BBAFE3F87FBDF57A1 /* Pods-Runner.release.xcconfig */,
28C4A8DBB53D5AC72DDB74A4 /* Pods-Runner.profile.xcconfig */,
1615A82960D98F780416FDE7 /* Pods-RunnerTests.debug.xcconfig */,
782E66FE4E292DCFD0D1B7D2 /* Pods-RunnerTests.release.xcconfig */,
9D7B0FFE63E0BD5D67D7758D /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
942752100DC29827FE94D2B2 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
2594AD7A0161CA1A66191F05 /* Frameworks */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
8FBC28ABA859CAFFB7CFEFA3 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
122816C3E1F98387D8AA58D3 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
B1B2C3D41234567890ABCDEF /* MinimalIcon@2x.png in Resources */,
B1B2C3D41234567890ABCDF0 /* MinimalIcon@3x.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
122816C3E1F98387D8AA58D3 /* [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;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
8FBC28ABA859CAFFB7CFEFA3 /* [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;
};
942752100DC29827FE94D2B2 /* [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-RunnerTests-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;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
AA11BB22CC33DD44EE550101 /* QlyraVideo.swift in Sources */,
AA11BB22CC33DD44EE550102 /* QlyraVideoNote.swift in Sources */,
AA11BB22CC33DD44EE550103 /* QlyraNotifications.swift in Sources */,
AA11BB22CC33DD44EE550105 /* QlyraClipboard.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
PRODUCT_BUNDLE_IDENTIFIER = ru.ikar.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1615A82960D98F780416FDE7 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = ru.ikar.app.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 782E66FE4E292DCFD0D1B7D2 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = ru.ikar.app.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9D7B0FFE63E0BD5D67D7758D /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = ru.ikar.app.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
PRODUCT_BUNDLE_IDENTIFIER = ru.ikar.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
PRODUCT_BUNDLE_IDENTIFIER = ru.ikar.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
+185
View File
@@ -0,0 +1,185 @@
import Flutter
import UIKit
final class QlyraStreamHandler: NSObject, FlutterStreamHandler {
private let onSink: (FlutterEventSink?) -> Void
init(onSink: @escaping (FlutterEventSink?) -> Void) {
self.onSink = onSink
}
func onListen(
withArguments arguments: Any?,
eventSink events: @escaping FlutterEventSink
) -> FlutterError? {
onSink(events)
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
onSink(nil)
return nil
}
}
@main
@objc class AppDelegate: FlutterAppDelegate {
private var channels: [FlutterMethodChannel] = []
private var eventChannels: [FlutterEventChannel] = []
private var streamHandlers: [QlyraStreamHandler] = []
private var videoNote: QlyraVideoNote?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
QlyraNotifications.shared.start()
if let controller = window?.rootViewController as? FlutterViewController {
let messenger = controller.binaryMessenger
registerAppIcon(messenger)
registerVideo(messenger)
registerVideoNote(messenger)
registerNotifications(messenger)
registerScreen(messenger)
registerClipboard(messenger)
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func method(_ name: String, _ messenger: FlutterBinaryMessenger,
_ handler: @escaping FlutterMethodCallHandler) {
let channel = FlutterMethodChannel(name: name, binaryMessenger: messenger)
channel.setMethodCallHandler(handler)
channels.append(channel)
}
private func events(_ name: String, _ messenger: FlutterBinaryMessenger,
_ onSink: @escaping (FlutterEventSink?) -> Void) {
let handler = QlyraStreamHandler(onSink: onSink)
let channel = FlutterEventChannel(name: name, binaryMessenger: messenger)
channel.setStreamHandler(handler)
streamHandlers.append(handler)
eventChannels.append(channel)
}
private func registerAppIcon(_ messenger: FlutterBinaryMessenger) {
method("ru.qlyra.app/app_icon", messenger) { call, result in
switch call.method {
case "getAppIcon":
result(UIApplication.shared.alternateIconName)
case "setAppIcon":
let requested = (call.arguments as? [String: Any])?["name"] as? String
let iconName: String? = (requested?.isEmpty ?? true) ? nil : requested
guard UIApplication.shared.supportsAlternateIcons else {
result(FlutterError(code: "UNSUPPORTED",
message: "Alternate icons are not supported",
details: nil))
return
}
guard UIApplication.shared.alternateIconName != iconName else {
result(nil)
return
}
UIApplication.shared.setAlternateIconName(iconName) { error in
DispatchQueue.main.async {
if let error = error {
result(FlutterError(code: "APPLY_FAILED",
message: error.localizedDescription,
details: nil))
} else {
result(nil)
}
}
}
default:
result(FlutterMethodNotImplemented)
}
}
}
private func registerVideo(_ messenger: FlutterBinaryMessenger) {
method("ru.qlyra.app/video", messenger) { call, result in
QlyraVideo.shared.handle(call, result: result)
}
}
private func registerVideoNote(_ messenger: FlutterBinaryMessenger) {
guard let textures = registrar(forPlugin: "QlyraVideoNote")?.textures() else { return }
method("ru.qlyra.app/video_note", messenger) { [weak self] call, result in
guard let self = self else { return }
switch call.method {
case "permission":
QlyraVideoNote.requestPermission(result)
case "init":
let args = call.arguments as? [String: Any] ?? [:]
self.videoNote?.dispose()
let recorder = QlyraVideoNote(registry: textures)
self.videoNote = recorder
recorder.initialize(
front: (args["front"] as? NSNumber)?.boolValue ?? true,
cameraId: args["cameraId"] as? String,
edge: (args["size"] as? NSNumber)?.intValue ?? 480,
fps: (args["fps"] as? NSNumber)?.intValue ?? 30,
result: result)
case "start":
self.withRecorder(result) { $0.start(result: result) }
case "switch":
self.withRecorder(result) { $0.switchCamera(result: result) }
case "torch":
let on = ((call.arguments as? [String: Any])?["on"] as? NSNumber)?.boolValue ?? false
self.withRecorder(result) { $0.setTorch(on: on, result: result) }
case "stop":
self.withRecorder(result) { $0.stop(result: result) }
case "dispose":
self.videoNote?.dispose()
self.videoNote = nil
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
}
private func withRecorder(_ result: @escaping FlutterResult,
_ body: (QlyraVideoNote) -> Void) {
guard let recorder = videoNote else {
result(FlutterError(code: "NOT_READY", message: "recorder not initialized", details: nil))
return
}
body(recorder)
}
private func registerScreen(_ messenger: FlutterBinaryMessenger) {
method("ru.qlyra.app/screen", messenger) { call, result in
switch call.method {
case "setKeepAwake":
let enabled = ((call.arguments as? [String: Any])?["enabled"] as? NSNumber)?.boolValue ?? false
DispatchQueue.main.async {
UIApplication.shared.isIdleTimerDisabled = enabled
result(nil)
}
default:
result(FlutterMethodNotImplemented)
}
}
}
private func registerClipboard(_ messenger: FlutterBinaryMessenger) {
method("ru.qlyra.app/clipboard", messenger) { call, result in
QlyraClipboard.handle(call, result: result)
}
}
private func registerNotifications(_ messenger: FlutterBinaryMessenger) {
method("ru.qlyra.app/notifications", messenger) { call, result in
QlyraNotifications.shared.handle(call, result: result)
}
events("ru.qlyra.app/notification_events", messenger) { sink in
QlyraNotifications.shared.attach(sink)
}
}
}
@@ -0,0 +1 @@
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
+118
View File
@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>FlutterDeepLinkingEnabled</key>
<false/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>ИКАР</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>ИКАР</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundleAlternateIcons</key>
<dict>
<key>MinimalIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>MinimalIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
</dict>
</dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSFaceIDUsageDescription</key>
<string>Face ID разблокирует ИКАР вместо кода-пароля.</string>
<key>NSCameraUsageDescription</key>
<string>Камера нужна для съёмки фото и видео в чатах, сканирования QR-кода входа и работы веб-приложений.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Микрофон нужен для записи голосовых сообщений, видео и звонков.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Геолокация нужна, чтобы отправлять ваше текущее местоположение в чатах.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Доступ к галерее нужен, чтобы отправлять фото и видео в чатах.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Доступ к галерее нужен, чтобы сохранять полученные фото и видео.</string>
<key>NSContactsUsageDescription</key>
<string>Доступ к контактам нужен, чтобы показывать имена собеседников так, как они записаны в вашей телефонной книге.</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>ru.ikar.app</string>
<key>CFBundleURLSchemes</key>
<array>
<string>qlyra</string>
<string>max</string>
</array>
</dict>
</array>
<key>CFBundleLocalizations</key>
<array>
<string>ru</string>
<string>en</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>tel</string>
<string>telprompt</string>
<string>sms</string>
<string>mailto</string>
<string>maps</string>
<string>comgooglemaps</string>
<string>yandexmaps</string>
<string>yandexnavi</string>
</array>
</dict>
</plist>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+115
View File
@@ -0,0 +1,115 @@
import Flutter
import UIKit
enum QlyraClipboard {
private static let cacheDirectory = "clipboard_in"
private static let retention: TimeInterval = 24 * 60 * 60
private static let recodeQuality: CGFloat = 0.95
private static let passthrough: [(type: String, ext: String)] = [
("public.png", "png"),
("public.jpeg", "jpg"),
("com.compuserve.gif", "gif"),
("org.webmproject.webp", "webp"),
]
static func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "hasMedia":
result(UIPasteboard.general.hasImages)
case "read":
read(result)
default:
result(FlutterMethodNotImplemented)
}
}
private static func read(_ result: @escaping FlutterResult) {
let pasteboard = UIPasteboard.general
guard pasteboard.hasImages else {
result(nil)
return
}
let items = pasteboard.items
DispatchQueue.global(qos: .userInitiated).async {
let images = items.compactMap(image(in:))
let paths = images.isEmpty ? [] : store(images)
DispatchQueue.main.async {
result(paths.isEmpty ? nil : ["files": paths])
}
}
}
private static func image(in item: [String: Any]) -> (data: Data, ext: String)? {
for entry in passthrough {
guard let data = item[entry.type] as? Data, !data.isEmpty else { continue }
return (data, entry.ext)
}
for value in item.values {
if let image = value as? UIImage, let jpeg = recoded(image) {
return (jpeg, "jpg")
}
if let data = value as? Data,
let image = UIImage(data: data),
let jpeg = recoded(image) {
return (jpeg, "jpg")
}
}
return nil
}
private static func recoded(_ image: UIImage) -> Data? {
guard let jpeg = image.jpegData(compressionQuality: recodeQuality), !jpeg.isEmpty else {
return nil
}
return jpeg
}
private static func store(_ images: [(data: Data, ext: String)]) -> [String] {
guard let root = cacheRoot() else { return [] }
prune(root)
let stamp = Int(Date().timeIntervalSince1970 * 1000)
var paths: [String] = []
for (index, payload) in images.enumerated() {
let file = root.appendingPathComponent("paste_\(stamp)_\(index).\(payload.ext)")
do {
try payload.data.write(to: file, options: .atomic)
paths.append(file.path)
} catch {
NSLog("QlyraClipboard: cannot store a pasted image: \(error)")
}
}
return paths
}
private static func cacheRoot() -> URL? {
let manager = FileManager.default
guard let caches = manager.urls(for: .cachesDirectory, in: .userDomainMask).first else {
return nil
}
let root = caches.appendingPathComponent(cacheDirectory, isDirectory: true)
do {
try manager.createDirectory(at: root, withIntermediateDirectories: true)
} catch {
NSLog("QlyraClipboard: cannot create the paste cache: \(error)")
return nil
}
return root
}
private static func prune(_ root: URL) {
let manager = FileManager.default
let cutoff = Date().addingTimeInterval(-retention)
guard let entries = try? manager.contentsOfDirectory(
at: root,
includingPropertiesForKeys: [.contentModificationDateKey]
) else { return }
for entry in entries {
let values = try? entry.resourceValues(forKeys: [.contentModificationDateKey])
if let modified = values?.contentModificationDate, modified > cutoff { continue }
try? manager.removeItem(at: entry)
}
}
}
+105
View File
@@ -0,0 +1,105 @@
import Flutter
import UIKit
import UserNotifications
final class QlyraNotifications: NSObject {
static let shared = QlyraNotifications()
private static let chatKeys = ["qlyra_chat", "chatId", "chat_id"]
private var sink: FlutterEventSink?
private var pendingChatId: Int64 = 0
private var activeChatId: Int64 = 0
func start() {
UNUserNotificationCenter.current().delegate = self
}
func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "consumeInitialChat":
let chatId = pendingChatId
pendingChatId = 0
result(chatId > 0 ? NSNumber(value: chatId) : nil)
case "setActiveChat":
activeChatId = Self.chatId(from: call.arguments)
dismissDelivered(chatId: activeChatId)
result(nil)
case "clearActiveChat":
activeChatId = 0
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
func attach(_ sink: FlutterEventSink?) {
self.sink = sink
}
func deliver(chatId: Int64) {
guard chatId > 0 else { return }
if let sink = sink {
sink(NSNumber(value: chatId))
} else {
pendingChatId = chatId
}
}
private func dismissDelivered(chatId: Int64) {
guard chatId > 0 else { return }
let center = UNUserNotificationCenter.current()
center.getDeliveredNotifications { delivered in
let identifiers = delivered
.filter { Self.chatId(from: $0.request.content.userInfo) == chatId }
.map { $0.request.identifier }
guard !identifiers.isEmpty else { return }
center.removeDeliveredNotifications(withIdentifiers: identifiers)
}
}
private static func chatId(from raw: Any?) -> Int64 {
if let number = raw as? NSNumber { return number.int64Value }
if let text = raw as? String { return Int64(text) ?? 0 }
if let map = raw as? [AnyHashable: Any] {
for key in chatKeys {
if let value = map[key], let parsed = optionalChatId(value) { return parsed }
}
}
return 0
}
private static func optionalChatId(_ raw: Any) -> Int64? {
if let number = raw as? NSNumber { return number.int64Value }
if let text = raw as? String { return Int64(text) }
return nil
}
}
extension QlyraNotifications: UNUserNotificationCenterDelegate {
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
let chatId = Self.chatId(from: notification.request.content.userInfo)
if chatId > 0, chatId == activeChatId {
completionHandler([])
return
}
if #available(iOS 14.0, *) {
completionHandler([.banner, .list, .sound, .badge])
} else {
completionHandler([.alert, .sound, .badge])
}
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
deliver(chatId: Self.chatId(from: response.notification.request.content.userInfo))
completionHandler()
}
}
+333
View File
@@ -0,0 +1,333 @@
import AVFoundation
import CoreImage
import Flutter
import UIKit
private struct VideoExportSpec {
let input: String
let output: String
let startMs: Int?
let endMs: Int?
let removeAudio: Bool
let rotationDegrees: Double
let flipH: Bool
let crop: [Double]?
let outWidth: Int
let outHeight: Int
let rgbMatrix: [Double]?
let overlay: String?
let centerSquare: Bool
init?(_ arguments: Any?) {
guard let args = arguments as? [String: Any],
let input = args["input"] as? String,
let output = args["output"] as? String else { return nil }
self.input = input
self.output = output
startMs = (args["startMs"] as? NSNumber)?.intValue
endMs = (args["endMs"] as? NSNumber)?.intValue
removeAudio = (args["removeAudio"] as? NSNumber)?.boolValue ?? false
rotationDegrees = (args["rotationDegrees"] as? NSNumber)?.doubleValue ?? 0
flipH = (args["flipH"] as? NSNumber)?.boolValue ?? false
crop = (args["crop"] as? [NSNumber])?.map { $0.doubleValue }
outWidth = (args["outWidth"] as? NSNumber)?.intValue ?? 0
outHeight = (args["outHeight"] as? NSNumber)?.intValue ?? 0
rgbMatrix = (args["rgbMatrix"] as? [NSNumber])?.map { $0.doubleValue }
overlay = args["overlay"] as? String
centerSquare = false
}
init(input: String, output: String, edge: Int) {
self.input = input
self.output = output
startMs = nil
endMs = nil
removeAudio = false
rotationDegrees = 0
flipH = false
crop = nil
outWidth = edge
outHeight = edge
rgbMatrix = nil
overlay = nil
centerSquare = true
}
}
final class QlyraVideo {
static let shared = QlyraVideo()
private let queue = DispatchQueue(label: "ru.qlyra.app.video", qos: .userInitiated)
private var session: AVAssetExportSession?
private var cancelled = false
func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "probe":
probe(call.arguments, result)
case "frames":
frames(call.arguments, result)
case "cropSquare":
cropSquare(call.arguments, result)
case "edit":
guard let spec = VideoExportSpec(call.arguments) else {
result(FlutterError(code: "BAD_ARGS", message: "input/output required", details: nil))
return
}
export(spec) { ok in result(NSNumber(value: ok)) }
case "editProgress":
let value = session.map { Int(($0.progress * 100).rounded()) } ?? -1
result(NSNumber(value: value))
case "editCancel":
cancelled = true
session?.cancelExport()
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
private func probe(_ arguments: Any?, _ result: @escaping FlutterResult) {
guard let args = arguments as? [String: Any], let input = args["input"] as? String else {
result(FlutterError(code: "BAD_ARGS", message: "input required", details: nil))
return
}
queue.async {
let asset = AVURLAsset(url: URL(fileURLWithPath: input))
guard let track = asset.tracks(withMediaType: .video).first else {
Self.reply(result, nil)
return
}
let size = track.naturalSize.applying(track.preferredTransform)
let seconds = CMTimeGetSeconds(asset.duration)
let durationMs = seconds.isFinite && seconds > 0 ? Int((seconds * 1000).rounded()) : 0
let fps = Double(track.nominalFrameRate)
let payload: [String: Any] = [
"width": Int(abs(size.width).rounded()),
"height": Int(abs(size.height).rounded()),
"durationMs": durationMs,
"fps": fps > 0 ? fps : 30.0,
"hasAudio": !asset.tracks(withMediaType: .audio).isEmpty,
]
Self.reply(result, payload)
}
}
private func frames(_ arguments: Any?, _ result: @escaping FlutterResult) {
guard let args = arguments as? [String: Any],
let input = args["input"] as? String,
let times = args["times"] as? [NSNumber] else {
result(FlutterError(code: "BAD_ARGS", message: "input/times required", details: nil))
return
}
let edge = (args["size"] as? NSNumber)?.intValue ?? 256
let precise = (args["precise"] as? NSNumber)?.boolValue ?? false
queue.async {
let asset = AVURLAsset(url: URL(fileURLWithPath: input))
let generator = AVAssetImageGenerator(asset: asset)
generator.appliesPreferredTrackTransform = true
generator.maximumSize = CGSize(width: edge, height: edge)
if precise {
generator.requestedTimeToleranceBefore = .zero
generator.requestedTimeToleranceAfter = .zero
}
var output: [Any] = []
for time in times {
let at = CMTime(value: CMTimeValue(time.int64Value), timescale: 1000)
guard let cgImage = try? generator.copyCGImage(at: at, actualTime: nil),
let data = UIImage(cgImage: cgImage).jpegData(compressionQuality: 0.9) else {
output.append(NSNull())
continue
}
output.append(FlutterStandardTypedData(bytes: data))
}
Self.reply(result, output)
}
}
private func cropSquare(_ arguments: Any?, _ result: @escaping FlutterResult) {
guard let args = arguments as? [String: Any],
let input = args["input"] as? String,
let output = args["output"] as? String else {
result(FlutterError(code: "BAD_ARGS", message: "input/output required", details: nil))
return
}
let edge = (args["size"] as? NSNumber)?.intValue ?? 480
export(VideoExportSpec(input: input, output: output, edge: edge)) { ok in
if ok {
result(output)
} else {
result(FlutterError(code: "TRANSCODE_FAILED", message: "export failed", details: nil))
}
}
}
private func export(_ spec: VideoExportSpec, completion: @escaping (Bool) -> Void) {
queue.async {
self.cancelled = false
let asset = AVURLAsset(url: URL(fileURLWithPath: spec.input))
guard let videoTrack = asset.tracks(withMediaType: .video).first else {
Self.reply { completion(false) }
return
}
let totalSeconds = CMTimeGetSeconds(asset.duration)
let start = CMTime(value: CMTimeValue(spec.startMs ?? 0), timescale: 1000)
let endMs = spec.endMs ?? (totalSeconds.isFinite ? Int((totalSeconds * 1000).rounded()) : 0)
let end = CMTime(value: CMTimeValue(max(endMs, spec.startMs ?? 0)), timescale: 1000)
let range = CMTimeRange(start: start, end: end)
guard range.duration.seconds > 0 else {
Self.reply { completion(false) }
return
}
let composition = AVMutableComposition()
guard let compositionVideo = composition.addMutableTrack(
withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
Self.reply { completion(false) }
return
}
do {
try compositionVideo.insertTimeRange(range, of: videoTrack, at: .zero)
} catch {
Self.reply { completion(false) }
return
}
compositionVideo.preferredTransform = videoTrack.preferredTransform
if !spec.removeAudio,
let audioTrack = asset.tracks(withMediaType: .audio).first,
let compositionAudio = composition.addMutableTrack(
withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) {
try? compositionAudio.insertTimeRange(range, of: audioTrack, at: .zero)
}
let natural = videoTrack.naturalSize.applying(videoTrack.preferredTransform)
let outWidth = spec.outWidth > 0 ? spec.outWidth : Int(abs(natural.width).rounded())
let outHeight = spec.outHeight > 0 ? spec.outHeight : Int(abs(natural.height).rounded())
guard outWidth > 0, outHeight > 0 else {
Self.reply { completion(false) }
return
}
let transform = videoTrack.preferredTransform
let overlay = spec.overlay.flatMap { UIImage(contentsOfFile: $0) }.flatMap { CIImage(image: $0) }
let renderSize = CGSize(width: outWidth, height: outHeight)
let videoComposition = AVMutableVideoComposition(asset: composition) { request in
let image = Self.render(
request.sourceImage,
spec: spec,
transform: transform,
overlay: overlay,
renderSize: renderSize)
request.finish(with: image, context: nil)
}
videoComposition.renderSize = renderSize
let outputURL = URL(fileURLWithPath: spec.output)
try? FileManager.default.removeItem(at: outputURL)
guard let session = AVAssetExportSession(
asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
Self.reply { completion(false) }
return
}
session.outputURL = outputURL
session.outputFileType = .mp4
session.videoComposition = videoComposition
session.shouldOptimizeForNetworkUse = true
self.session = session
session.exportAsynchronously {
let ok = session.status == .completed && !self.cancelled
self.session = nil
if !ok { try? FileManager.default.removeItem(at: outputURL) }
Self.reply { completion(ok) }
}
}
}
private static func render(
_ source: CIImage,
spec: VideoExportSpec,
transform: CGAffineTransform,
overlay: CIImage?,
renderSize: CGSize
) -> CIImage {
var image = normalized(source.transformed(by: transform))
if spec.flipH {
image = normalized(image.transformed(by: CGAffineTransform(scaleX: -1, y: 1)))
}
if abs(spec.rotationDegrees) > 0.01 {
let radians = CGFloat(spec.rotationDegrees * .pi / 180)
image = normalized(image.transformed(by: CGAffineTransform(rotationAngle: radians)))
}
if spec.centerSquare {
let extent = image.extent
let side = min(extent.width, extent.height)
image = normalized(image.cropped(to: CGRect(
x: extent.midX - side / 2,
y: extent.midY - side / 2,
width: side,
height: side)))
} else if let crop = spec.crop, crop.count == 4 {
let extent = image.extent
let left = CGFloat((crop[0] + 1) / 2)
let right = CGFloat((crop[1] + 1) / 2)
let bottom = CGFloat((1 - crop[2]) / 2)
let top = CGFloat((1 - crop[3]) / 2)
let rect = CGRect(
x: extent.minX + left * extent.width,
y: extent.minY + (1 - bottom) * extent.height,
width: max(1, (right - left) * extent.width),
height: max(1, (bottom - top) * extent.height))
image = normalized(image.cropped(to: rect))
}
let extent = image.extent
if extent.width > 0, extent.height > 0 {
image = image.transformed(by: CGAffineTransform(
scaleX: renderSize.width / extent.width,
y: renderSize.height / extent.height))
image = normalized(image)
}
if let matrix = spec.rgbMatrix, matrix.count == 16,
let filter = CIFilter(name: "CIColorMatrix") {
filter.setValue(image, forKey: kCIInputImageKey)
filter.setValue(vector(matrix, 0, 4, 8), forKey: "inputRVector")
filter.setValue(vector(matrix, 1, 5, 9), forKey: "inputGVector")
filter.setValue(vector(matrix, 2, 6, 10), forKey: "inputBVector")
filter.setValue(CIVector(x: 0, y: 0, z: 0, w: 1), forKey: "inputAVector")
filter.setValue(vector(matrix, 12, 13, 14), forKey: "inputBiasVector")
if let output = filter.outputImage { image = output }
}
if let overlay = overlay {
image = overlay.composited(over: image)
}
return image.cropped(to: CGRect(origin: .zero, size: renderSize))
}
private static func vector(_ m: [Double], _ x: Int, _ y: Int, _ z: Int) -> CIVector {
CIVector(x: CGFloat(m[x]), y: CGFloat(m[y]), z: CGFloat(m[z]), w: 0)
}
private static func normalized(_ image: CIImage) -> CIImage {
let extent = image.extent
guard extent.origin != .zero else { return image }
return image.transformed(
by: CGAffineTransform(translationX: -extent.origin.x, y: -extent.origin.y))
}
private static func reply(_ result: @escaping FlutterResult, _ value: Any?) {
DispatchQueue.main.async { result(value) }
}
private static func reply(_ block: @escaping () -> Void) {
DispatchQueue.main.async(execute: block)
}
}
+414
View File
@@ -0,0 +1,414 @@
import AVFoundation
import CoreImage
import Flutter
import UIKit
final class QlyraVideoNoteTexture: NSObject, FlutterTexture {
private let lock = NSLock()
private var latest: CVPixelBuffer?
func push(_ buffer: CVPixelBuffer) {
lock.lock()
latest = buffer
lock.unlock()
}
func copyPixelBuffer() -> Unmanaged<CVPixelBuffer>? {
lock.lock()
defer { lock.unlock() }
guard let buffer = latest else { return nil }
return Unmanaged.passRetained(buffer)
}
}
final class QlyraVideoNote: NSObject {
private let registry: FlutterTextureRegistry
private let queue = DispatchQueue(label: "ru.qlyra.app.videonote", qos: .userInitiated)
private let ciContext = CIContext(options: [.useSoftwareRenderer: false])
private let session = AVCaptureSession()
private let videoOutput = AVCaptureVideoDataOutput()
private let audioOutput = AVCaptureAudioDataOutput()
private let texture = QlyraVideoNoteTexture()
private var textureId: Int64 = 0
private var deviceInput: AVCaptureDeviceInput?
private var audioInput: AVCaptureDeviceInput?
private var position: AVCaptureDevice.Position = .front
private var preferredCameraId: String?
private var edge: Int = 480
private var fps: Int = 30
private var pixelBufferPool: CVPixelBufferPool?
private var writer: AVAssetWriter?
private var writerVideo: AVAssetWriterInput?
private var writerAudio: AVAssetWriterInput?
private var adaptor: AVAssetWriterInputPixelBufferAdaptor?
private var outputURL: URL?
private var recording = false
private var sessionStarted = false
init(registry: FlutterTextureRegistry) {
self.registry = registry
super.init()
}
static func requestPermission(_ result: @escaping FlutterResult) {
AVCaptureDevice.requestAccess(for: .video) { video in
AVCaptureDevice.requestAccess(for: .audio) { audio in
DispatchQueue.main.async {
result(["camera": NSNumber(value: video), "microphone": NSNumber(value: audio)])
}
}
}
}
func initialize(
front: Bool, cameraId: String?, edge: Int, fps: Int,
result: @escaping FlutterResult
) {
self.position = front ? .front : .back
self.preferredCameraId = cameraId?.isEmpty == false ? cameraId : nil
self.edge = max(16, edge)
self.fps = max(1, fps)
queue.async {
guard AVCaptureDevice.authorizationStatus(for: .video) == .authorized else {
Self.fail(result, "NO_CAMERA_PERMISSION", "camera permission required")
return
}
guard AVCaptureDevice.authorizationStatus(for: .audio) == .authorized else {
Self.fail(result, "NO_MIC_PERMISSION", "microphone permission required")
return
}
do {
try self.configureSession()
} catch {
Self.fail(result, "NO_CAMERA", error.localizedDescription)
return
}
self.session.startRunning()
DispatchQueue.main.async {
if self.textureId == 0 {
self.textureId = self.registry.register(self.texture)
}
result(["textureId": NSNumber(value: self.textureId), "hasFlash": self.hasTorch()])
}
}
}
func switchCamera(result: @escaping FlutterResult) {
queue.async {
self.position = self.position == .front ? .back : .front
self.preferredCameraId = nil
do {
try self.configureSession()
} catch {
Self.fail(result, "NO_CAMERA", error.localizedDescription)
return
}
DispatchQueue.main.async { result(nil) }
}
}
func setTorch(on: Bool, result: @escaping FlutterResult) {
queue.async {
guard let device = self.deviceInput?.device, device.hasTorch else {
DispatchQueue.main.async { result(NSNumber(value: false)) }
return
}
var applied = false
if (try? device.lockForConfiguration()) != nil {
device.torchMode = on ? .on : .off
device.unlockForConfiguration()
applied = on
}
DispatchQueue.main.async { result(NSNumber(value: applied)) }
}
}
func start(result: @escaping FlutterResult) {
queue.async {
guard !self.recording else {
DispatchQueue.main.async { result(nil) }
return
}
guard AVCaptureDevice.authorizationStatus(for: .audio) == .authorized else {
Self.fail(result, "NO_MIC_PERMISSION", "microphone permission required")
return
}
do {
try self.prepareWriter()
} catch {
Self.fail(result, "START_FAILED", error.localizedDescription)
return
}
self.sessionStarted = false
self.recording = true
DispatchQueue.main.async { result(nil) }
}
}
func stop(result: @escaping FlutterResult) {
queue.async {
guard self.recording, let writer = self.writer else {
Self.fail(result, "NOT_RECORDING", "no active recording")
return
}
self.recording = false
self.writerVideo?.markAsFinished()
self.writerAudio?.markAsFinished()
let url = self.outputURL
writer.finishWriting {
let ok = writer.status == .completed
self.writer = nil
self.writerVideo = nil
self.writerAudio = nil
self.adaptor = nil
self.outputURL = nil
let path: String? = ok ? url?.path : nil
DispatchQueue.main.async {
result(path)
}
}
}
}
func dispose() {
queue.sync {
if self.recording {
self.recording = false
self.writerVideo?.markAsFinished()
self.writerAudio?.markAsFinished()
self.writer?.cancelWriting()
self.writer = nil
}
if self.session.isRunning { self.session.stopRunning() }
for input in self.session.inputs { self.session.removeInput(input) }
for output in self.session.outputs { self.session.removeOutput(output) }
self.deviceInput = nil
self.audioInput = nil
self.pixelBufferPool = nil
}
if textureId != 0 {
registry.unregisterTexture(textureId)
textureId = 0
}
}
private func hasTorch() -> Bool {
deviceInput?.device.hasTorch ?? false
}
private func configureSession() throws {
session.beginConfiguration()
defer { session.commitConfiguration() }
if let existing = deviceInput {
session.removeInput(existing)
deviceInput = nil
}
if let id = preferredCameraId,
let chosen = AVCaptureDevice(uniqueID: id),
chosen.position != .unspecified {
position = chosen.position
}
guard let device = preferredCameraId.flatMap({ AVCaptureDevice(uniqueID: $0) })
?? AVCaptureDevice.default(
.builtInWideAngleCamera, for: .video, position: position)
?? AVCaptureDevice.default(for: .video) else {
throw NSError(domain: "QlyraVideoNote", code: 1,
userInfo: [NSLocalizedDescriptionKey: "no camera found"])
}
let input = try AVCaptureDeviceInput(device: device)
guard session.canAddInput(input) else {
throw NSError(domain: "QlyraVideoNote", code: 2,
userInfo: [NSLocalizedDescriptionKey: "camera input rejected"])
}
session.addInput(input)
deviceInput = input
if session.canSetSessionPreset(.hd1280x720) {
session.sessionPreset = .hd1280x720
}
if audioInput == nil,
let microphone = AVCaptureDevice.default(for: .audio),
let input = try? AVCaptureDeviceInput(device: microphone),
session.canAddInput(input) {
session.addInput(input)
audioInput = input
}
if !session.outputs.contains(videoOutput) {
videoOutput.videoSettings = [
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
]
videoOutput.alwaysDiscardsLateVideoFrames = true
videoOutput.setSampleBufferDelegate(self, queue: queue)
if session.canAddOutput(videoOutput) { session.addOutput(videoOutput) }
}
if !session.outputs.contains(audioOutput) {
audioOutput.setSampleBufferDelegate(self, queue: queue)
if session.canAddOutput(audioOutput) { session.addOutput(audioOutput) }
}
if let connection = videoOutput.connection(with: .video) {
if connection.isVideoOrientationSupported {
connection.videoOrientation = .portrait
}
if connection.isVideoMirroringSupported {
connection.automaticallyAdjustsVideoMirroring = false
connection.isVideoMirrored = position == .front
}
}
if (try? device.lockForConfiguration()) != nil {
let duration = CMTimeMake(value: 1, timescale: Int32(fps))
if device.activeFormat.videoSupportedFrameRateRanges.contains(where: {
$0.minFrameRate <= Double(fps) && Double(fps) <= $0.maxFrameRate
}) {
device.activeVideoMinFrameDuration = duration
device.activeVideoMaxFrameDuration = duration
}
device.unlockForConfiguration()
}
pixelBufferPool = Self.makePool(edge: edge)
}
private func prepareWriter() throws {
let directory = FileManager.default.temporaryDirectory
let url = directory.appendingPathComponent(
"qlyra_note_\(Int(Date().timeIntervalSince1970 * 1000)).mp4")
try? FileManager.default.removeItem(at: url)
let writer = try AVAssetWriter(outputURL: url, fileType: .mp4)
let videoSettings: [String: Any] = [
AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoWidthKey: edge,
AVVideoHeightKey: edge,
AVVideoCompressionPropertiesKey: [
AVVideoAverageBitRateKey: 1_024_000,
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
],
]
let video = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
video.expectsMediaDataInRealTime = true
guard writer.canAdd(video) else {
throw NSError(domain: "QlyraVideoNote", code: 3,
userInfo: [NSLocalizedDescriptionKey: "video input rejected"])
}
writer.add(video)
let audioSettings: [String: Any] = [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVNumberOfChannelsKey: 1,
AVSampleRateKey: 44100,
AVEncoderBitRateKey: 64000,
]
let audio = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings)
audio.expectsMediaDataInRealTime = true
if writer.canAdd(audio) { writer.add(audio) }
adaptor = AVAssetWriterInputPixelBufferAdaptor(
assetWriterInput: video,
sourcePixelBufferAttributes: [
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
kCVPixelBufferWidthKey as String: edge,
kCVPixelBufferHeightKey as String: edge,
])
guard writer.startWriting() else {
throw NSError(domain: "QlyraVideoNote", code: 4,
userInfo: [NSLocalizedDescriptionKey: "writer refused to start"])
}
self.writer = writer
self.writerVideo = video
self.writerAudio = writer.inputs.contains(audio) ? audio : nil
self.outputURL = url
}
private static func makePool(edge: Int) -> CVPixelBufferPool? {
let attributes: [String: Any] = [
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
kCVPixelBufferWidthKey as String: edge,
kCVPixelBufferHeightKey as String: edge,
kCVPixelBufferIOSurfacePropertiesKey as String: [String: Any](),
]
var pool: CVPixelBufferPool?
CVPixelBufferPoolCreate(kCFAllocatorDefault, nil, attributes as CFDictionary, &pool)
return pool
}
private func squareBuffer(from source: CVPixelBuffer) -> CVPixelBuffer? {
guard let pool = pixelBufferPool else { return nil }
var target: CVPixelBuffer?
guard CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &target)
== kCVReturnSuccess, let output = target else { return nil }
let image = CIImage(cvPixelBuffer: source)
let extent = image.extent
let side = min(extent.width, extent.height)
let cropped = image.cropped(to: CGRect(
x: extent.midX - side / 2,
y: extent.midY - side / 2,
width: side,
height: side))
let scale = CGFloat(edge) / side
let scaled = cropped
.transformed(by: CGAffineTransform(translationX: -cropped.extent.origin.x,
y: -cropped.extent.origin.y))
.transformed(by: CGAffineTransform(scaleX: scale, y: scale))
ciContext.render(scaled, to: output)
return output
}
private static func fail(_ result: @escaping FlutterResult, _ code: String, _ message: String) {
DispatchQueue.main.async {
result(FlutterError(code: code, message: message, details: nil))
}
}
}
extension QlyraVideoNote: AVCaptureVideoDataOutputSampleBufferDelegate,
AVCaptureAudioDataOutputSampleBufferDelegate {
func captureOutput(
_ output: AVCaptureOutput,
didOutput sampleBuffer: CMSampleBuffer,
from connection: AVCaptureConnection
) {
if output === audioOutput {
appendAudio(sampleBuffer)
return
}
guard let source = CMSampleBufferGetImageBuffer(sampleBuffer),
let square = squareBuffer(from: source) else { return }
texture.push(square)
let id = textureId
if id != 0 {
DispatchQueue.main.async { self.registry.textureFrameAvailable(id) }
}
guard recording, let writer = writer, let input = writerVideo,
let adaptor = adaptor else { return }
let timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
if !sessionStarted {
writer.startSession(atSourceTime: timestamp)
sessionStarted = true
}
guard input.isReadyForMoreMediaData else { return }
adaptor.append(square, withPresentationTime: timestamp)
}
private func appendAudio(_ sampleBuffer: CMSampleBuffer) {
guard recording, sessionStarted, let input = writerAudio,
input.isReadyForMoreMediaData else { return }
input.append(sampleBuffer)
}
}
+1
View File
@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)ru.ikar.app</string>
</array>
</dict>
</plist>
+12
View File
@@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}