Import IKAR messenger client and server
@@ -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
|
||||
@@ -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>
|
||||
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||
<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>
|
||||
@@ -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>
|
||||
@@ -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"}}
|
||||
|
After Width: | Height: | Size: 445 KiB |
|
After Width: | Height: | Size: 807 B |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 10 KiB |
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
}
|
||||