diff --git a/iOS/Podspec/TUIChat.podspec b/iOS/Podspec/TUIChat.podspec index bd52c716d..83c0d4162 100644 --- a/iOS/Podspec/TUIChat.podspec +++ b/iOS/Podspec/TUIChat.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'TUIChat' - spec.version = '6.0.1992' + spec.version = '6.1.2155' spec.platform = :ios spec.ios.deployment_target = '9.0' spec.license = { :type => 'Proprietary', @@ -17,98 +17,9 @@ Pod::Spec.new do |spec| spec.requires_arc = true - spec.source = { :http => 'https://sdk-im-1252463788.cos.ap-hongkong.myqcloud.com/download/tuikit/6.0.1992/ios/TUIChat.zip'} - spec.dependency 'TUICore','6.0.1992' - - spec.subspec 'VoiceConvert' do |voiceConvert| - voiceConvert.vendored_libraries = ['**/TUIChat/VoiceConvert/*.a'] - voiceConvert.source_files = '**/TUIChat/VoiceConvert/*.{h,m,mm}' - end - - spec.subspec 'Cell' do |cell| - cell.subspec 'CellData' do |cellData| - cellData.subspec 'Base' do |base| - base.source_files = '**/TUIChat/Cell/CellData/Base/*.{h,m,mm}' - base.dependency 'TUIChat/VoiceConvert' - end - cellData.subspec 'Chat' do |chat| - chat.source_files = '**/TUIChat/Cell/CellData/Chat/*.{h,m,mm}' - chat.dependency 'TUIChat/Cell/CellData/Base' - end - cellData.subspec 'Custom' do |custom| - custom.source_files = '**/TUIChat/Cell/CellData/Custom/*.{h,m,mm}' - custom.dependency 'TUIChat/Cell/CellData/Chat' - end - cellData.subspec 'Reply' do |reply| - reply.source_files = '**/TUIChat/Cell/CellData/Reply/*.{h,m,mm}' - reply.dependency 'TUIChat/Cell/CellData/Chat' - end - end - - cell.subspec 'CellUI' do |cellUI| - cellUI.subspec 'Base' do |base| - base.source_files = '**/TUIChat/Cell/CellUI/Base/*.{h,m,mm}' - base.dependency 'TUIChat/Cell/CellData' - end - cellUI.subspec 'Chat' do |chat| - chat.source_files = '**/TUIChat/Cell/CellUI/Chat/*.{h,m,mm}' - chat.dependency 'TUIChat/Cell/CellUI/Base' - end - cellUI.subspec 'Custom' do |custom| - custom.source_files = '**/TUIChat/Cell/CellUI/Custom/*.{h,m,mm}' - custom.dependency 'TUIChat/Cell/CellUI/Chat' - end - cellUI.subspec 'Reply' do |reply| - reply.source_files = '**/TUIChat/Cell/CellUI/Reply/*.{h,m,mm}' - reply.dependency 'TUIChat/Cell/CellUI/Chat' - end - end - end - - spec.subspec 'DataProvider' do |dataProvider| - dataProvider.source_files = '**/TUIChat/DataProvider/*.{h,m,mm}' - dataProvider.dependency 'TUIChat/Cell' - end - - spec.subspec 'UI' do |ui| - ui.subspec 'Base' do |base| - base.subspec 'Camera' do |camera| - camera.source_files = '**/TUIChat/UI/Base/Camera/*.{h,m,mm}' - camera.dependency 'TUIChat/DataProvider' - end - base.subspec 'Media' do |media| - media.source_files = '**/TUIChat/UI/Base/Media/*.{h,m,mm}' - media.dependency 'TUIChat/UI/Base/Camera' - end - base.subspec 'Pop' do |pop| - pop.source_files = '**/TUIChat/UI/Base/Pop/*.{h,m,mm}' - end - end - ui.subspec 'Forward' do |forward| - forward.source_files = '**/TUIChat/UI/Forward/*.{h,m,mm}' - forward.dependency 'TUIChat/UI/Base' - end - ui.subspec 'Input' do |input| - input.source_files = '**/TUIChat/UI/Input/*.{h,m,mm}' - input.dependency 'TUIChat/UI/Base' - end - ui.subspec 'Pendency' do |pendency| - pendency.source_files = '**/TUIChat/UI/Pendency/*.{h,m,mm}' - pendency.dependency 'TUIChat/UI/Base' - end - ui.subspec 'Chat' do |chat| - chat.source_files = '**/TUIChat/UI/Chat/*.{h,m,mm}' - chat.dependency 'TUIChat/UI/Forward' - chat.dependency 'TUIChat/UI/Input' - chat.dependency 'TUIChat/UI/Pendency' - end - end - - spec.subspec 'Service' do |service| - service.source_files = '**/TUIChat/Service/*.{h,m,mm}' - service.dependency 'TUIChat/UI' - end - + spec.source = { :http => 'https://sdk-im-1252463788.cos.ap-hongkong.myqcloud.com/download/tuikit/6.1.2155/ios/TUIChat.zip'} + spec.dependency 'TUICore','6.1.2155' + spec.source_files = '**/TUIChat/**/*.{h,m,mm}' spec.resource = [ '**/TUIChat/Resources/*.bundle' ] diff --git a/iOS/Podspec/TUIContact.podspec b/iOS/Podspec/TUIContact.podspec index 72b99fbae..3b614388f 100644 --- a/iOS/Podspec/TUIContact.podspec +++ b/iOS/Podspec/TUIContact.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'TUIContact' - spec.version = '6.0.1992' + spec.version = '6.1.2155' spec.platform = :ios spec.ios.deployment_target = '9.0' spec.license = { :type => 'Proprietary', @@ -16,12 +16,12 @@ Pod::Spec.new do |spec| spec.requires_arc = true - spec.source = { :http => 'https://sdk-im-1252463788.cos.ap-hongkong.myqcloud.com/download/tuikit/6.0.1992/ios/TUIContact.zip'} + spec.source = { :http => 'https://sdk-im-1252463788.cos.ap-hongkong.myqcloud.com/download/tuikit/6.1.2155/ios/TUIContact.zip'} spec.subspec 'Cell' do |cell| cell.subspec 'CellData' do |cellData| cellData.source_files = '**/TUIContact/Cell/CellData/*.{h,m,mm}' - cellData.dependency 'TUICore','6.0.1992' + cellData.dependency 'TUICore','6.1.2155' end cell.subspec 'CellUI' do |cellUI| diff --git a/iOS/Podspec/TUIConversation.podspec b/iOS/Podspec/TUIConversation.podspec index 7dfabb507..32b12d628 100644 --- a/iOS/Podspec/TUIConversation.podspec +++ b/iOS/Podspec/TUIConversation.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'TUIConversation' - spec.version = '6.0.1992' + spec.version = '6.1.2155' spec.platform = :ios spec.ios.deployment_target = '9.0' spec.license = { :type => 'Proprietary', @@ -16,12 +16,12 @@ Pod::Spec.new do |spec| spec.requires_arc = true - spec.source = { :http => 'https://sdk-im-1252463788.cos.ap-hongkong.myqcloud.com/download/tuikit/6.0.1992/ios/TUIConversation.zip'} + spec.source = { :http => 'https://sdk-im-1252463788.cos.ap-hongkong.myqcloud.com/download/tuikit/6.1.2155/ios/TUIConversation.zip'} spec.subspec 'Cell' do |cell| cell.subspec 'CellData' do |cellData| cellData.source_files = '**/TUIConversation/Cell/CellData/*.{h,m,mm}' - cellData.dependency 'TUICore','6.0.1992' + cellData.dependency 'TUICore','6.1.2155' end cell.subspec 'CellUI' do |cellUI| diff --git a/iOS/Podspec/TUICore.podspec b/iOS/Podspec/TUICore.podspec index bdabd07ab..9423916aa 100644 --- a/iOS/Podspec/TUICore.podspec +++ b/iOS/Podspec/TUICore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'TUICore' - spec.version = '6.0.1992' + spec.version = '6.1.2155' spec.platform = :ios spec.ios.deployment_target = '9.0' spec.license = { :type => 'Proprietary', @@ -14,13 +14,13 @@ Pod::Spec.new do |spec| spec.summary = 'TUICore' spec.xcconfig = { 'VALID_ARCHS' => 'armv7 arm64 x86_64', } - spec.dependency 'TXIMSDK_Plus_iOS','6.0.1975' + spec.dependency 'TXIMSDK_Plus_iOS','6.1.2155' spec.dependency 'ReactiveObjC' spec.dependency 'SDWebImage' spec.requires_arc = true - spec.source = { :http => 'https://sdk-im-1252463788.cos.ap-hongkong.myqcloud.com/download/tuikit/6.0.1992/ios/TUICore.zip'} + spec.source = { :http => 'https://sdk-im-1252463788.cos.ap-hongkong.myqcloud.com/download/tuikit/6.1.2155/ios/TUICore.zip'} spec.source_files = '**/TUICore/*.{h,m,mm,c}' spec.resource = [ '**/TUICore/Resources/*.bundle' diff --git a/iOS/Podspec/TUIGroup.podspec b/iOS/Podspec/TUIGroup.podspec index 69bb57e01..8ddd24067 100644 --- a/iOS/Podspec/TUIGroup.podspec +++ b/iOS/Podspec/TUIGroup.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'TUIGroup' - spec.version = '6.0.1992' + spec.version = '6.1.2155' spec.platform = :ios spec.ios.deployment_target = '9.0' spec.license = { :type => 'Proprietary', @@ -16,12 +16,12 @@ Pod::Spec.new do |spec| spec.requires_arc = true - spec.source = { :http => 'https://sdk-im-1252463788.cos.ap-hongkong.myqcloud.com/download/tuikit/6.0.1992/ios/TUIGroup.zip'} + spec.source = { :http => 'https://sdk-im-1252463788.cos.ap-hongkong.myqcloud.com/download/tuikit/6.1.2155/ios/TUIGroup.zip'} spec.subspec 'Cell' do |cell| cell.subspec 'CellData' do |cellData| cellData.source_files = '**/TUIGroup/Cell/CellData/*.{h,m,mm}' - cellData.dependency 'TUICore','6.0.1992' + cellData.dependency 'TUICore','6.1.2155' end cell.subspec 'CellUI' do |cellUI| diff --git a/iOS/Podspec/TUISearch.podspec b/iOS/Podspec/TUISearch.podspec index 8ed415d36..c3977412c 100644 --- a/iOS/Podspec/TUISearch.podspec +++ b/iOS/Podspec/TUISearch.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'TUISearch' - spec.version = '6.0.1992' + spec.version = '6.1.2155' spec.platform = :ios spec.ios.deployment_target = '9.0' spec.license = { :type => 'Proprietary', @@ -16,12 +16,12 @@ Pod::Spec.new do |spec| spec.requires_arc = true - spec.source = { :http => 'https://sdk-im-1252463788.cos.ap-hongkong.myqcloud.com/download/tuikit/6.0.1992/ios/TUISearch.zip'} + spec.source = { :http => 'https://sdk-im-1252463788.cos.ap-hongkong.myqcloud.com/download/tuikit/6.1.2155/ios/TUISearch.zip'} spec.subspec 'Cell' do |cell| cell.subspec 'CellData' do |cellData| cellData.source_files = '**/TUISearch/Cell/CellData/*.{h,m,mm}' - cellData.dependency 'TUICore','6.0.1992' + cellData.dependency 'TUICore','6.1.2155' end cell.subspec 'CellUI' do |cellUI| diff --git a/iOS/TUIKit/TUICalling/Example/App/AppDelegate.swift b/iOS/TUIKit/TUICalling/Example/App/AppDelegate.swift new file mode 100644 index 000000000..fa374bc1b --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/AppDelegate.swift @@ -0,0 +1,141 @@ +// +// AppDelegate.swift +// TRTCCalling +// +// Created by adams on 2021/5/7. +// + +import UIKit +import TUICalling +import UserNotifications + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + let LICENCEURL = "https://liteav.sdk.qcloud.com/app/res/licence/liteav/ios/TXLiveSDK_Enterprise_trtc.licence" + let LICENCEKEY = "9bc74ac7bfd07ea392e8fdff2ba5678a" + + func setLicence() { + TXLiveBase.setLicenceURL(LICENCEURL, key: LICENCEKEY) + } + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + registerRemoteNotifications(with: application) + + // 监听推送 + V2TIMManager.sharedInstance().setAPNSListener(self) + // 监听会话的未读数 + V2TIMManager.sharedInstance().addConversationListener(listener: self) + // 设置 SDK 的 Licence 下载 url 和 key + setLicence() + + return true + } + + // MARK: UISceneSession Lifecycle + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + func showMainViewController() { + let mainViewController = MainViewController.init() + let rootVC = UINavigationController.init(rootViewController: mainViewController) + + if let keyWindow = SceneDelegate.getCurrentWindow() { + keyWindow.rootViewController = rootVC + keyWindow.makeKeyAndVisible() + } else { + debugPrint("window show MainViewController error") + } + } + + func showLoginViewController() { + let loginVC = TRTCLoginViewController.init() + let nav = UINavigationController(rootViewController: loginVC) + + if let keyWindow = SceneDelegate.getCurrentWindow() { + keyWindow.rootViewController = nav + keyWindow.makeKeyAndVisible() + } else { + debugPrint("window show LoginViewController error") + } + } + + // MARK: - 注册远程推送 DeviceToken + func registerRemoteNotifications(with application: UIApplication) { + if #available(iOS 10, *) { + let notificationCenter = UNUserNotificationCenter.current() + notificationCenter.requestAuthorization(options: [.alert,.badge,.sound]) { (isGrand, error) in + DispatchQueue.main.async { + if error == nil && isGrand { + print("用户允许了推送权限") + }else{ + print("用户拒绝了推送权限") + } + } + } + } else { + UIApplication.shared.registerUserNotificationSettings(.init(types:[.alert,.badge,.sound], categories:nil)) + } + + // 注册远程通知,获得device Token + UIApplication.shared.registerForRemoteNotifications() + } + + // MARK: - + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + print("didRegisterForRemoteNotificationsWithDeviceToken success") + AppUtils.shared.deviceToken = deviceToken + } + + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + print("didFailToRegisterForRemoteNotificationsWithError-error = \(error)") + } + + // MARK: 当用户点击通知时,会触发 + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + completionHandler() + } + + // MARK: iOS10以前的设备 registerUserNotificationSettings会触发notificationSettings + @available(iOS, introduced: 8.0, deprecated: 11.0) + func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) { + if notificationSettings.types.isEmpty { + print("用户拒绝了推送权限") + }else{ + print("用户允许了推送权限") + } + } + + // MARK: 适配iOS10之前的设备 didReceiveRemoteNotification 当用户点击通知时,会触发 + @available(iOS, introduced: 8.0, deprecated: 11.0) + func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) { + if application.applicationState == .inactive { + print("点击推送唤起APP userInfo=\(userInfo)") + }else{ + print("前台收到推送 userInfo=\(userInfo)") + } + } +} + +extension AppDelegate: V2TIMConversationListener { + func onTotalUnreadMessageCountChanged(_ totalUnreadCount: UInt64) { + // 会话未读总数变更通知, 可以在此处自定义处理逻辑 + } +} + +extension AppDelegate: V2TIMAPNSListener { + // 程序进后台后,自定义 APP 的未读数,如果不处理,APP 未读数默认为所有会话未读数之和 + func onSetAPPUnreadCount() -> UInt32 { + return 0 + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AccentColor.colorset/Contents.json b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Contents.json b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d10e79133 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,62 @@ +{ + "images" : [ + { + "filename" : "Icon-Notification@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-Notification@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "Icon-Small@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-Small@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "Icon-Small-40@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-Small-40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "Icon-60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "Icon-60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png new file mode 100644 index 000000000..c1b61bdce Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png new file mode 100644 index 000000000..749a8f71e Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png new file mode 100644 index 000000000..373638352 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png new file mode 100644 index 000000000..99aa700d3 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png new file mode 100644 index 000000000..6348283f4 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png new file mode 100644 index 000000000..c1b61bdce Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png new file mode 100644 index 000000000..5c8f2c7a4 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png new file mode 100644 index 000000000..c54a37664 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/icon.png b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/icon.png new file mode 100644 index 000000000..2c04ba480 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/AppIcon.appiconset/icon.png differ diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/Contents.json b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/Contents.json similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/Contents.json rename to iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/Contents.json diff --git a/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/voiceroom_cover1.imageset/Contents.json b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/voiceroom_cover1.imageset/Contents.json new file mode 100644 index 000000000..f00ff2731 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/voiceroom_cover1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "avatar0_100.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/voiceroom_cover1.imageset/avatar0_100.png b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/voiceroom_cover1.imageset/avatar0_100.png new file mode 100644 index 000000000..4ab41bae7 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Assets.xcassets/voiceroom_cover1.imageset/avatar0_100.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Base.lproj/LaunchScreen.storyboard b/iOS/TUIKit/TUICalling/Example/App/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..99b5cda67 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/TUIKit/TUICalling/Example/App/Base.lproj/LaunchScreen_en.storyboard b/iOS/TUIKit/TUICalling/Example/App/Base.lproj/LaunchScreen_en.storyboard new file mode 100644 index 000000000..a89d12208 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Base.lproj/LaunchScreen_en.storyboard @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/TUIKit/TUICalling/Example/App/Base.lproj/Main.storyboard b/iOS/TUIKit/TUICalling/Example/App/Base.lproj/Main.storyboard new file mode 100644 index 000000000..25a763858 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/Contents.json b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/calling_back.imageset/Contents.json b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/calling_back.imageset/Contents.json new file mode 100644 index 000000000..3325f1197 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/calling_back.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "calling_back.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "calling_back@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "calling_back@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/calling_back.imageset/calling_back.png b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/calling_back.imageset/calling_back.png new file mode 100644 index 000000000..c1b18270d Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/calling_back.imageset/calling_back.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/calling_back.imageset/calling_back@2x.png b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/calling_back.imageset/calling_back@2x.png new file mode 100644 index 000000000..e0d845d1a Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/calling_back.imageset/calling_back@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/calling_back.imageset/calling_back@3x.png b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/calling_back.imageset/calling_back@3x.png new file mode 100644 index 000000000..ed992662c Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/calling_back.imageset/calling_back@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/cm_search_white.imageset/Contents.json b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/cm_search_white.imageset/Contents.json new file mode 100644 index 000000000..302f0f544 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/cm_search_white.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "cm_search_white.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "cm_search_white@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "cm_search_white@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/cm_search_white.imageset/cm_search_white.png b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/cm_search_white.imageset/cm_search_white.png new file mode 100644 index 000000000..8a65b1574 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/cm_search_white.imageset/cm_search_white.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/cm_search_white.imageset/cm_search_white@2x.png b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/cm_search_white.imageset/cm_search_white@2x.png new file mode 100644 index 000000000..ec266a4c8 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/cm_search_white.imageset/cm_search_white@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/cm_search_white.imageset/cm_search_white@3x.png b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/cm_search_white.imageset/cm_search_white@3x.png new file mode 100644 index 000000000..a00fae680 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/cm_search_white.imageset/cm_search_white@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/noSearchMembers.imageset/Contents.json b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/noSearchMembers.imageset/Contents.json new file mode 100644 index 000000000..38c48011c --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/noSearchMembers.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "noSearchMembers.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "noSearchMembers@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "noSearchMembers@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/noSearchMembers.imageset/noSearchMembers.png b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/noSearchMembers.imageset/noSearchMembers.png new file mode 100644 index 000000000..34d7822b2 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/noSearchMembers.imageset/noSearchMembers.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/noSearchMembers.imageset/noSearchMembers@2x.png b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/noSearchMembers.imageset/noSearchMembers@2x.png new file mode 100644 index 000000000..7bf9e8150 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/noSearchMembers.imageset/noSearchMembers@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/noSearchMembers.imageset/noSearchMembers@3x.png b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/noSearchMembers.imageset/noSearchMembers@3x.png new file mode 100644 index 000000000..82eb99274 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/assets/CallingContact.xcassets/noSearchMembers.imageset/noSearchMembers@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/localized/CallingLocalized.swift b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/localized/CallingLocalized.swift new file mode 100644 index 000000000..b2e84c699 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/localized/CallingLocalized.swift @@ -0,0 +1,14 @@ +// +// CallingLocalized.swift +// TRTCScene +// +// Created by adams on 2021/5/20. +// + +import Foundation + +let CallingLocalizeTableName = "CallingLocalized" + +func CallingLocalize(_ key: String) -> String { + return localizeFromTable(key: key, table: CallingLocalizeTableName) +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/localized/en.lproj/CallingLocalized.strings b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/localized/en.lproj/CallingLocalized.strings new file mode 100644 index 000000000..f43d9a715 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/localized/en.lproj/CallingLocalized.strings @@ -0,0 +1,34 @@ +/* + CallingLocalized.strings + TRTCScene + + Created by adams on 2021/5/20. + +*/ + +"Demo.TRTC.calling.yourID" = "Your UserId: "; +"Demo.TRTC.calling.searchID" = "Search UserId"; +"Demo.TRTC.calling.searching" = "Search"; +"Demo.TRTC.calling.searchandcall" = "Search for a registered user\n to start a call"; +"Demo.TRTC.calling.callingbegan" = "Joined call"; +"Demo.TRTC.calling.callingcancel" = "Call cancelled"; +"Demo.TRTC.calling.callingtimeout" = "Call timed out"; +"Demo.TRTC.calling.callingrefuse" = "declined the call"; +"Demo.TRTC.calling.callingleave" = "left the call"; +"Demo.TRTC.calling.callingnoresponse" = "didn’t answer"; +"Demo.TRTC.calling.callingbusy" = "is busy"; +"Demo.TRTC.calling.searchingfailed" = "Search failed"; +"Demo.TRTC.calling.cantinviteself" = "You cannot invite yourself."; +"Demo.TRTC.Streaming.call" = "Call"; +"Demo.TRTC.calling.add" = "Add"; +"Demo.TRTC.calling.delete" = "Delete"; +"Demo.TRTC.calling.addedUser" = "User Added"; +"Demo.TRTC.calling.done" = "Done"; +"Demo.TRTC.calling.tips" = "No users added, please search to add users"; +"App.PortalViewController.areyousureloginout" = "Are you sure you want to log out?"; +"App.PortalViewController.cancel" = "Cancel"; +"App.PortalViewController.determine" = "Yes"; +"Demo.TRTC.calling.calltitle" = "Audio/Video Call"; +"Demo.TRTC.calling.callType" = "Please select the calling method"; +"Audio Call" = "Voice Call"; +"Video Call" = "Video Call"; diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/localized/zh-Hans.lproj/CallingLocalized.strings b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/localized/zh-Hans.lproj/CallingLocalized.strings new file mode 100644 index 000000000..964a8c4c9 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/localized/zh-Hans.lproj/CallingLocalized.strings @@ -0,0 +1,30 @@ +/* + CallingLocalized.strings + TRTCScene + + Created by adams on 2021/5/20. + +*/ + +"Demo.TRTC.calling.yourID" = "您的ID:"; +"Demo.TRTC.calling.searchID" = "搜索ID"; +"Demo.TRTC.calling.searching" = "搜索"; +"Demo.TRTC.calling.searchandcall" = "搜索添加已注册用户\n以发起通话"; +"Demo.TRTC.calling.callingbegan" = "进入通话"; +"Demo.TRTC.calling.callingcancel" = "通话取消"; +"Demo.TRTC.calling.callingtimeout" = "通话超时"; +"Demo.TRTC.calling.callingrefuse" = "拒绝了通话"; +"Demo.TRTC.calling.callingleave" = "离开了通话"; +"Demo.TRTC.calling.callingnoresponse" = "未响应"; +"Demo.TRTC.calling.callingbusy" = "忙线"; +"Demo.TRTC.calling.searchingfailed" = "查询失败"; +"Demo.TRTC.calling.cantinviteself" = "不能邀请自己"; +"Demo.TRTC.calling.add" = "添加"; +"Demo.TRTC.calling.delete" = "删除"; +"Demo.TRTC.calling.addedUser" = "已添加成员"; +"Demo.TRTC.calling.done" = "完成"; +"Demo.TRTC.calling.tips" = "未添加成员,请搜索添加成员"; +"Demo.TRTC.Streaming.call" = "呼叫"; + +"Demo.TRTC.Calling.othernetworkpoor" = "对方网络不佳"; +"Demo.TRTC.Calling.yournetworkpoor" = "己方网络不佳"; diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/resource/phone_ringing.mp3 b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/phone_ringing.mp3 new file mode 100644 index 000000000..166c027ce Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Calling/resource/phone_ringing.mp3 differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/ui/CallingSelectUserTableViewCell.swift b/iOS/TUIKit/TUICalling/Example/App/Calling/ui/CallingSelectUserTableViewCell.swift new file mode 100644 index 000000000..d7037dea8 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Calling/ui/CallingSelectUserTableViewCell.swift @@ -0,0 +1,106 @@ +// +// CallingSelectUserTableViewCell.swift +// TRTCScene +// +// Created by adams on 2021/5/20. +// + +import UIKit +import ImSDK_Plus +import TUICalling + +public enum CallingSelectUserButtonType { + case call + case add + case delete +} + +public class CallingSelectUserTableViewCell: UITableViewCell { + private var isViewReady = false + private var buttonAction: (() -> Void)? + lazy var userImageView: UIImageView = { + let img = UIImageView() + return img + }() + + lazy var nameLabel: UILabel = { + let label = UILabel() + label.textColor = UIColor.black + label.backgroundColor = UIColor.clear + return label + }() + + let rightButton: UIButton = { + let button = UIButton(type: .custom) + button.backgroundColor = UIColor("006EFF") + button.setTitleColor(.white, for: .normal) + button.layer.cornerRadius = 15 + return button + }() + + public override func didMoveToWindow() { + super.didMoveToWindow() + guard !isViewReady else { return } + isViewReady = true + contentView.addSubview(userImageView) + userImageView.snp.remakeConstraints { (make) in + make.leading.equalToSuperview().offset(20) + make.width.height.equalTo(50) + make.centerY.equalTo(self) + } + + contentView.addSubview(nameLabel) + nameLabel.snp.remakeConstraints { (make) in + make.leading.equalTo(userImageView.snp.trailing).offset(12) + make.trailing.top.bottom.equalTo(self) + } + + contentView.addSubview(rightButton) + rightButton.snp.makeConstraints { (make) in + make.centerY.equalToSuperview() + make.width.equalTo(60) + make.height.equalTo(30) + make.right.equalToSuperview().offset(-20) + } + + rightButton.addTarget(self, action: #selector(callAction(_:)), for: .touchUpInside) + } + + public override func prepareForReuse() { + super.prepareForReuse() + self.buttonAction = nil + } + + public func config(model: V2TIMUserFullInfo, type: CallingSelectUserButtonType, selected: Bool = false, action: (() -> Void)? = nil) { + backgroundColor = UIColor.clear + var btnName = "" + + let faceURL = model.faceURL ?? DEFAULT_AVATETR + if let imageURL = URL(string: faceURL) { + userImageView.kf.setImage(with: .network(imageURL), placeholder: TUICommonUtil.getBundleImage(withName: "userIcon")) + } + + userImageView.layer.masksToBounds = true + userImageView.layer.cornerRadius = 25 + nameLabel.text = model.nickName != "" ? model.nickName : model.userID + buttonAction = action + + switch type { + case .call: + btnName = CallingLocalize("Demo.TRTC.Streaming.call") + case .add: + btnName = CallingLocalize("Demo.TRTC.calling.add") + case .delete: + btnName = CallingLocalize("Demo.TRTC.calling.delete") + } + + rightButton.setTitle(btnName, for: .normal) + } + + @objc + func callAction(_ sender: UIButton) { + if let action = self.buttonAction { + action() + } + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/ui/TRTCCallingContactView.swift b/iOS/TUIKit/TUICalling/Example/App/Calling/ui/TRTCCallingContactView.swift new file mode 100644 index 000000000..55676d7df --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Calling/ui/TRTCCallingContactView.swift @@ -0,0 +1,346 @@ +// +// TRTCCallingContactView.swift +// TRTCAPP_AppStore +// +// Created by noah on 2021/12/21. +// + +import UIKit +import Foundation +import Toast_Swift +import TUICalling +import ImSDK_Plus + +enum TRTCCallingUserRemoveReason: UInt32 { + case leave = 0 + case reject + case noresp + case busy +} + +public class TRTCCallingContactView: UIView { + var selectedFinished: (([V2TIMUserFullInfo])->Void)? = nil + var btnType: CallingSelectUserButtonType = .call + @objc var callType: CallType = .audio + /// 是否展示搜索结果 + var shouldShowSearchResult: Bool = false { + didSet { + if oldValue != shouldShowSearchResult { + selectTable.reloadData() + } + } + } + + /// 搜索结果model + var searchResult: V2TIMUserFullInfo? = nil + + let searchContainerView: UIView = { + let view = UIView(frame: .zero) + view.backgroundColor = UIColor.clear + return view + }() + + let searchBar: UISearchBar = { + let searchBar = UISearchBar() + searchBar.backgroundImage = UIImage() + searchBar.placeholder = .searchPhoneNumberText + searchBar.backgroundColor = UIColor(hex: "F4F5F9") + searchBar.barTintColor = UIColor.clear + searchBar.returnKeyType = .search + searchBar.keyboardType = .phonePad + searchBar.layer.cornerRadius = 20 + return searchBar + }() + + /// 搜索按钮 + lazy var searchBtn: UIButton = { + let done = UIButton(type: .custom) + done.setTitle(.searchText, for: .normal) + done.setTitleColor(.white, for: .normal) + done.titleLabel?.font = UIFont.systemFont(ofSize: 16) + done.backgroundColor = UIColor(hex: "006EFF") + done.clipsToBounds = true + done.layer.cornerRadius = 20 + return done + }() + + lazy var userInfoMarkView: UIView = { + let view = UIView(frame: .zero) + view.backgroundColor = UIColor(hex: "999999") + view.layer.cornerRadius = 1.5 + return view + }() + + let userInfoLabel: UILabel = { + let label = UILabel(frame: .zero) + label.text = "\(String.yourUserNameText)\("\(V2TIMManager.sharedInstance()?.getLoginUser() ?? "")")" + label.font = UIFont.systemFont(ofSize: 16.0) + label.textColor = UIColor.black + label.textAlignment = .left + return label + }() + + /// 选择列表 + lazy var selectTable: UITableView = { + let table = UITableView(frame: CGRect.zero, style: .plain) + table.tableFooterView = UIView(frame: .zero) + table.backgroundColor = UIColor.clear + table.register(CallingSelectUserTableViewCell.classForCoder(), forCellReuseIdentifier: "CallingSelectUserTableViewCell") + table.delegate = self + table.dataSource = self + return table + }() + + let kUserBorder: CGFloat = 44.0 + let kUserSpacing: CGFloat = 2 + let kUserPanelLeftSpacing: CGFloat = 28 + + /// 搜索记录为空时,提示 + lazy var noSearchImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "noSearchMembers")) + imageView.isUserInteractionEnabled = false + return imageView + }() + + lazy var noMembersTip: UILabel = { + let label = UILabel() + label.text = .backgroundTipsText + label.numberOfLines = 2 + label.textAlignment = NSTextAlignment.center + label.textColor = UIColor(hex: "BBBBBB") + return label + }() + + public init(frame: CGRect = .zero, type: CallingSelectUserButtonType, selectHandle: @escaping ([V2TIMUserFullInfo])->Void) { + super.init(frame: frame) + btnType = type + selectedFinished = selectHandle + backgroundColor = UIColor.white + setupUI() + registerButtonTouchEvents() + hiddenNoMembersImg(isHidden: false) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + debugPrint("deinit \(self)") + NotificationCenter.default.removeObserver(self) + } + + var lastNetworkQualityCallTime: Date? +} + +extension TRTCCallingContactView { + + func setupUI() { + constructViewHierarchy() + activateConstraints() + setupUIStyle() + hiddenNoMembersImg(isHidden: false) + selectTable.reloadData() + } + + func constructViewHierarchy() { + // 添加SearchBar + addSubview(searchContainerView) + searchContainerView.addSubview(searchBar) + searchContainerView.addSubview(searchBtn) + + addSubview(userInfoMarkView) + addSubview(userInfoLabel) + addSubview(selectTable) + // selectTable.isHidden = true + addSubview(noSearchImageView) + addSubview(noMembersTip) + } + + func activateConstraints() { + searchContainerView.snp.makeConstraints { (make) in + make.top.equalToSuperview().offset(20) + make.leading.equalTo(20) + make.trailing.equalTo(-20) + make.height.equalTo(40) + } + searchBar.snp.makeConstraints { (make) in + make.top.leading.bottom.equalToSuperview() + make.trailing.equalTo(searchBtn.snp.leading).offset(-10) + } + searchBtn.snp.makeConstraints { (make) in + make.top.trailing.bottom.equalToSuperview() + make.width.equalTo(60) + } + userInfoMarkView.snp.makeConstraints { (make) in + make.centerY.equalTo(userInfoLabel) + make.leading.equalTo(20) + make.size.equalTo(CGSize(width: 3, height: 12)) + } + userInfoLabel.snp.makeConstraints { (make) in + make.top.equalTo(searchContainerView.snp.bottom).offset(20) + make.leading.equalTo(userInfoMarkView).offset(8) + make.trailing.equalTo(-20) + make.height.equalTo(20) + } + selectTable.snp.makeConstraints { (make) in + make.top.equalTo(userInfoLabel.snp.bottom).offset(10) + make.leading.trailing.equalToSuperview() + make.bottom.equalToSuperview() + } + noSearchImageView.snp.makeConstraints { (make) in + make.top.equalTo(self.bounds.size.height/3.0) + make.leading.equalTo(self.bounds.size.width*154.0/375) + make.trailing.equalTo(-self.bounds.size.width*154.0/375) + make.height.equalTo(self.bounds.size.width*67.0/375) + } + noMembersTip.snp.makeConstraints { (make) in + make.top.equalTo(noSearchImageView.snp.bottom) + make.width.equalTo(self.bounds.size.width) + make.height.equalTo(60) + } + } + + func setupUIStyle() { + if let textfield = searchBar.value(forKey: "searchField") as? UITextField { + textfield.layer.cornerRadius = 10.0 + textfield.layer.masksToBounds = true + textfield.textColor = UIColor.black + textfield.backgroundColor = UIColor.clear + textfield.leftViewMode = .always + } + ToastManager.shared.position = .bottom + } + + func showCallVC(users: [String]) { + var type: TUICallingType = .video + if callType == .audio { + type = .audio + } + TUICalling.shareInstance().call(userIDs: users, type: type) + } + + @objc func hiddenNoMembersImg(isHidden: Bool) { + selectTable.isHidden = !isHidden + noSearchImageView.isHidden = isHidden + noMembersTip.isHidden = isHidden + } +} + +extension TRTCCallingContactView: UITextFieldDelegate { + + public func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { + hiddenNoMembersImg(isHidden: false) + } + + public func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { + searchBar.resignFirstResponder() + if let input = searchBar.text, input.count > 0 { + searchUser(input: input) + } + } + + public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + if searchBar.text == nil || searchBar.text?.count ?? 0 == 0 { + //show recent table + shouldShowSearchResult = false + hiddenNoMembersImg(isHidden: false) + } + + if (searchBar.text?.count ?? 0) > 11 { + searchBar.text = (searchBar.text as NSString?)?.substring(to: 11) + } + } + + public func searchUser(input: String) { + V2TIMManager.sharedInstance()?.getUsersInfo([input], succ: { [weak self] (userInfos) in + guard let `self` = self, let userInfo = userInfos?.first else { return } + self.searchResult = userInfo + self.shouldShowSearchResult = true + self.selectTable.reloadData() + self.hiddenNoMembersImg(isHidden: true) + }, fail: { [weak self] (_, _) in + guard let self = self else {return} + self.searchResult = nil + self.makeToast(.failedSearchText) + }) + } +} + +extension TRTCCallingContactView: UITableViewDelegate, UITableViewDataSource { + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if shouldShowSearchResult { + return 1 + } + return 0 + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "CallingSelectUserTableViewCell") as! CallingSelectUserTableViewCell + cell.selectionStyle = .none + if shouldShowSearchResult { + if let V2TIMUserFullInfo = searchResult { + cell.config(model: V2TIMUserFullInfo, type: btnType, selected: false) { [weak self] in + guard let `self` = self else { return } + if V2TIMUserFullInfo.userID == V2TIMManager.sharedInstance()?.getLoginUser() { + self.makeToast(.cantInvateSelfText) + return + } + if let finish = self.selectedFinished { + finish([V2TIMUserFullInfo]) + } + } + } else { + debugPrint("not search result") + } + } + return cell + } + + public func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + view.tintColor = UIColor.clear + let header = view as! UITableViewHeaderFooterView + header.textLabel?.textColor = UIColor.white + } + + public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 60 + } + + public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + return true + } +} + +extension TRTCCallingContactView { + + private func registerButtonTouchEvents() { + searchBtn.addTarget(self, action: #selector(searchBtnTouchEvent(sender:)), for: .touchUpInside) + } + + @objc private func searchBtnTouchEvent(sender: UIButton) { + self.searchBar.resignFirstResponder() + if let input = self.searchBar.text, input.count > 0 { + self.searchUser(input: input) + } + } +} + +private extension String { + static let yourUserNameText = CallingLocalize("Demo.TRTC.calling.yourID") + static let searchPhoneNumberText = CallingLocalize("Demo.TRTC.calling.searchID") + static let searchText = CallingLocalize("Demo.TRTC.calling.searching") + static let backgroundTipsText = CallingLocalize("Demo.TRTC.calling.searchandcall") + static let enterConvText = CallingLocalize("Demo.TRTC.calling.callingbegan") + static let cancelConvText = CallingLocalize("Demo.TRTC.calling.callingcancel") + static let callTimeOutText = CallingLocalize("Demo.TRTC.calling.callingtimeout") + static let rejectToastText = CallingLocalize("Demo.TRTC.calling.callingrefuse") + static let leaveToastText = CallingLocalize("Demo.TRTC.calling.callingleave") + static let norespToastText = CallingLocalize("Demo.TRTC.calling.callingnoresponse") + static let busyToastText = CallingLocalize("Demo.TRTC.calling.callingbusy") + static let failedSearchText = CallingLocalize("Demo.TRTC.calling.searchingfailed") + static let cantInvateSelfText = CallingLocalize("Demo.TRTC.calling.cantinviteself") + static let localNetworkBadText = CallingLocalize("Demo.TRTC.Calling.yournetworkpoor") + static let remoteNetworkBadText = CallingLocalize("Demo.TRTC.Calling.othernetworkpoor") +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/ui/TRTCCallingContactViewController.swift b/iOS/TUIKit/TUICalling/Example/App/Calling/ui/TRTCCallingContactViewController.swift new file mode 100644 index 000000000..5555c1630 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Calling/ui/TRTCCallingContactViewController.swift @@ -0,0 +1,110 @@ +// +// TRTCCallingContactViewController.swift +// TXLiteAVDemo +// +// Created by abyyxwang on 2020/8/6. +// Copyright © 2020 Tencent. All rights reserved. +// + +import Foundation +import TUICalling +import UIKit + +public class TRTCCallingContactViewController: UIViewController, TUICallingListerner { + var selectedFinished: (([V2TIMUserFullInfo])->Void)? = nil + var callingVC = UIViewController() + @objc var callType: CallType = .audio + + lazy var callingContactView: TRTCCallingContactView = { + let callingContactView = TRTCCallingContactView(frame: .zero, type: .call) { [weak self] users in + guard let `self` = self else {return} + var userIds: [String] = [] + for V2TIMUserFullInfo in users { + userIds.append(V2TIMUserFullInfo.userID) + } + self.showCallVC(users: userIds) + } + + return callingContactView + }() + + public override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = UIColor.white + TUICalling.shareInstance().setCallingListener(listener: self) + TUICalling.shareInstance().enableCustomViewRoute(enable: true); + + let backBtn = UIButton(type: .custom) + backBtn.setImage(UIImage(named: "calling_back"), for: .normal) + backBtn.addTarget(self, action: #selector(backBtnClick), for: .touchUpInside) + let item = UIBarButtonItem(customView: backBtn) + item.tintColor = UIColor.black + navigationItem.leftBarButtonItem = item + + setupUI() + } + + @objc func backBtnClick() { + navigationController?.popViewController(animated: true) + } + + deinit { + debugPrint("deinit \(self)") + TUICalling.shareInstance().enableCustomViewRoute(enable: false); + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationController?.setNavigationBarHidden(false, animated: false) + } + + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + navigationController?.setNavigationBarHidden(false, animated: false) + } + + // MARK: - TUICallingListerner + + public func shouldShowOnCallView() -> Bool { + return true; + } + + public func callStart(userIDs: [String], type: TUICallingType, role: TUICallingRole, viewController: UIViewController?) { + + if let vc = viewController { + callingVC = vc; + vc.modalPresentationStyle = .fullScreen + self.present(vc, animated: false, completion: nil) + } + } + + public func callEnd(userIDs: [String], type: TUICallingType, role: TUICallingRole, totalTime: Float) { + callingVC.dismiss(animated: true) { + } + } + + public func onCallEvent(event: TUICallingEvent, type: TUICallingType, role: TUICallingRole, message: String) { + } +} + +extension TRTCCallingContactViewController { + + func setupUI() { + view.addSubview(callingContactView) + + callingContactView.snp.makeConstraints { (make) in + make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(20) + make.topMargin.equalTo(view) + make.leading.trailing.equalTo(view) + make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom) + } + } + + func showCallVC(users: [String]) { + var type: TUICallingType = .video + if callType == .audio { + type = .audio + } + TUICalling.shareInstance().call(userIDs: users, type: type) + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/ui/TRTCCallingCountryCodeView.swift b/iOS/TUIKit/TUICalling/Example/App/Calling/ui/TRTCCallingCountryCodeView.swift new file mode 100644 index 000000000..b64d909cf --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Calling/ui/TRTCCallingCountryCodeView.swift @@ -0,0 +1,323 @@ +// +// TRTCCallingCountryCodeView.swift +// TXLiteAVDemo +// +// Created by gg on 2021/4/30. +// Copyright © 2021 Tencent. All rights reserved. +// + +import Foundation +import TXAppBasic +import UIKit +import ImSDK_Plus + +public class TRTCCallingCountryAlert: TRTCCallingAlertContentView { + + lazy var dataSource: [TRTCCallingCountryModel] = { + let list = TRTCCallingCountryModel.loginCountryList + return list + }() + + lazy var tableView: UITableView = { + let tableView = UITableView(frame: .zero, style: .plain) + tableView.backgroundColor = UIColor.clear + return tableView + }() + + public var didSelect: ((_ model: TRTCCallingCountryModel)->())? + + override init(frame: CGRect = .zero) { + super.init(frame: frame) + titleLabel.text = .titleText + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func constructViewHierarchy() { + super.constructViewHierarchy() + contentView.addSubview(tableView) + } + override func activateConstraints() { + super.activateConstraints() + tableView.snp.makeConstraints { (make) in + make.top.equalTo(titleLabel.snp.bottom) + make.height.equalTo(ScreenHeight * 2 / 3) + make.leading.trailing.bottom.equalToSuperview() + } + } + override func bindInteraction() { + super.bindInteraction() + + tableView.register(TRTCCallingCountryListCell.self, forCellReuseIdentifier: "TRTCCallingCountryListCell") + tableView.dataSource = self + tableView.delegate = self + } +} + +extension TRTCCallingCountryAlert: UITableViewDataSource { + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return dataSource.count + } + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "TRTCCallingCountryListCell", for: indexPath) + if let scell = cell as? TRTCCallingCountryListCell { + let model = dataSource[indexPath.row] + scell.model = model + } + return cell + } + public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 40 + } +} +extension TRTCCallingCountryAlert: UITableViewDelegate { + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + if let action = didSelect { + action(dataSource[indexPath.row]) + } + dismiss() + } +} + +class TRTCCallingCountryListCell: UITableViewCell { + + var model: TRTCCallingCountryModel? { + didSet { + guard let model = model else { + return + } + titleLabel.text = model.countryName + descLabel.text = model.displayTitle + } + } + + lazy var titleLabel: UILabel = { + let label = UILabel(frame: .zero) + label.font = UIFont(name: "PingFangSC-Regular", size: 18) + label.textColor = UIColor.black + return label + }() + + lazy var descLabel: UILabel = { + let label = UILabel(frame: .zero) + label.font = UIFont(name: "PingFangSC-Regular", size: 16) + label.textColor = UIColor.black + label.alpha = 0.6 + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + backgroundColor = UIColor.clear + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var isViewReady = false + override func didMoveToWindow() { + super.didMoveToWindow() + guard !isViewReady else { + return + } + isViewReady = true + constructViewHierarchy() + activateConstraints() + bindInteraction() + } + + func constructViewHierarchy() { + contentView.addSubview(titleLabel) + contentView.addSubview(descLabel) + } + + func activateConstraints() { + titleLabel.snp.makeConstraints { (make) in + make.centerY.equalToSuperview() + make.leading.equalToSuperview().offset(20) + } + descLabel.snp.makeConstraints { (make) in + make.centerY.equalToSuperview() + make.trailing.equalToSuperview().offset(-20) + } + } + + func bindInteraction() { + + } +} +public class TRTCCallingAlertContentView: UIView { + lazy var bgView: UIView = { + let view = UIView(frame: .zero) + view.backgroundColor = UIColor.black + view.alpha = 0.6 + return view + }() + lazy var contentView: UIView = { + let view = UIView(frame: .zero) + view.backgroundColor = UIColor.white + return view + }() + + lazy var titleLabel: UILabel = { + let label = UILabel(frame: .zero) + label.textColor = UIColor.black + label.font = UIFont(name: "PingFangSC-Medium", size: 24) + return label + }() + + public var willDismiss: (()->())? + public var didDismiss: (()->())? + + public override init(frame: CGRect = .zero) { + super.init(frame: frame) + contentView.transform = CGAffineTransform(translationX: 0, y: ScreenHeight) + alpha = 0 + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var isViewReady = false + public override func didMoveToWindow() { + super.didMoveToWindow() + guard !isViewReady else { + return + } + isViewReady = true + constructViewHierarchy() + activateConstraints() + bindInteraction() + } + + public func show() { + UIView.animate(withDuration: 0.3) { + self.alpha = 1 + self.contentView.transform = .identity + } + } + + public func dismiss() { + if let action = willDismiss { + action() + } + UIView.animate(withDuration: 0.3) { + self.alpha = 0 + self.contentView.transform = CGAffineTransform(translationX: 0, y: ScreenHeight) + } completion: { (finish) in + if let action = self.didDismiss { + action() + } + self.removeFromSuperview() + } + } + + public override func touchesBegan(_ touches: Set, with event: UIEvent?) { + guard let point = touches.first?.location(in: self) else { + return + } + if !contentView.frame.contains(point) { + dismiss() + } + } + + public override func draw(_ rect: CGRect) { + super.draw(rect) + contentView.roundedRect(rect: contentView.bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 20, height: 20)) + } + + func constructViewHierarchy() { + addSubview(bgView) + addSubview(contentView) + contentView.addSubview(titleLabel) + } + func activateConstraints() { + bgView.snp.makeConstraints { (make) in + make.edges.equalToSuperview() + } + contentView.snp.makeConstraints { (make) in + make.leading.trailing.bottom.equalToSuperview() + } + titleLabel.snp.makeConstraints { (make) in + make.leading.equalToSuperview().offset(20) + make.top.equalToSuperview().offset(32) + } + } + func bindInteraction() { + + } +} + +public class TRTCCallingCountryModel: NSObject { + public let code: String + let displayEN: String + let displayZH: String + init(code: Int, displayEN: String, displayZH: String) { + self.code = String(code) + self.displayEN = displayEN + self.displayZH = displayZH + super.init() + } + + public var displayTitle: String { + get { + return "+\(code)" + } + } + + public var countryName: String { + get { + if isChineseLanguage() { + return displayZH + } + else { + return displayEN + } + } + } + + func isChineseLanguage() -> Bool { + guard let preferredLang = Bundle.main.preferredLocalizations.first else { + return false + } + return String(describing: preferredLang).hasPrefix("zh-") + } + + static var loginCountryList: [TRTCCallingCountryModel] { + get { + let list = loadLoginCountryJson() + var res : [TRTCCallingCountryModel] = [] + list.forEach { (dic) in + if let code = dic["code"] as? Int, let en = dic["en"] as? String, let zh = dic["zh"] as? String { + let model = TRTCCallingCountryModel(code: code, displayEN: en, displayZH: zh) + res.append(model) + } + } + return res + } + } + + static func loadLoginCountryJson() -> [[String:Any]] { + guard let path = Bundle.main.path(forResource: "LoginCountryList", ofType: "json") else { + return [] + } + guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { + return [] + } + let value = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves) + if let res = value as? [[String:Any]] { + return res + } + return [] + } +} + +/// MARK: - internationalization string +fileprivate extension String { + static let titleText = CallingLocalize("Demo.TRTC.Login.countrycode") +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Calling/ui/TRTCGroupCallingContactViewController.swift b/iOS/TUIKit/TUICalling/Example/App/Calling/ui/TRTCGroupCallingContactViewController.swift new file mode 100644 index 000000000..4835576e4 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Calling/ui/TRTCGroupCallingContactViewController.swift @@ -0,0 +1,253 @@ +// +// TRTCGroupCallingContactViewController.swift +// TRTCAPP_AppStore +// +// Created by noah on 2021/12/21. +// + +import UIKit +import Foundation +import Toast_Swift +import TUICalling +import ImSDK_Plus + +enum GroupCallingUserRemoveReason: UInt32 { + case leave = 0 + case reject + case noresp + case busy +} + +public class TRTCGroupCallingContactViewController: UIViewController { + var selectedFinished: (([V2TIMUserFullInfo])->Void)? = nil + + /// 存储已添加的用户id + var addedV2TIMUserFullInfo: [V2TIMUserFullInfo] = [] + + @objc var callType: CallType = .audio + + let addedTableTitle: UILabel = { + let label = UILabel(frame: .zero) + label.text = CallingLocalize("Demo.TRTC.calling.addedUser") + label.font = UIFont.systemFont(ofSize: 16.0) + label.textColor = UIColor.black + label.textAlignment = .left + return label + }() + + /// 已添加用户列表 + lazy var addedTable: UITableView = { + let table = UITableView(frame: CGRect.zero, style: .plain) + table.tableFooterView = UIView(frame: .zero) + table.backgroundColor = UIColor.clear + table.register(CallingSelectUserTableViewCell.classForCoder(), forCellReuseIdentifier: "CallingSelectUserTableViewCell") + table.delegate = self + table.dataSource = self + return table + }() + + /// 已添加成员为空时,提示 + lazy var noMembersTip: UILabel = { + let label = UILabel() + label.text = CallingLocalize("Demo.TRTC.calling.tips") + label.numberOfLines = 2 + label.textAlignment = NSTextAlignment.center + label.textColor = UIColor(hex: "BBBBBB") + return label + }() + + lazy var callingContactView: TRTCCallingContactView = { + let callingContactView = TRTCCallingContactView(frame: .zero, type: .add) { [weak self] users in + guard let `self` = self else {return} + self.adduser(users: users) + } + + return callingContactView + }() + + public override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = UIColor.white + // 左侧按钮 + let backBtn = UIButton(type: .custom) + backBtn.setImage(UIImage(named: "calling_back"), for: .normal) + backBtn.addTarget(self, action: #selector(backBtnClick), for: .touchUpInside) + let item = UIBarButtonItem(customView: backBtn) + item.tintColor = UIColor.black + navigationItem.leftBarButtonItem = item + // 右侧按钮 + let callBtn = UIButton(frame: CGRect(x: 0, y:0, width: 50, height: 30)) + callBtn.setTitle(CallingLocalize("Demo.TRTC.calling.done"), for: .normal) + callBtn.setTitleColor(UIColor.black, for: .normal) + callBtn.titleLabel?.font = UIFont.systemFont(ofSize: 16) + callBtn.addTarget(self, action: #selector(showCallVC), for: .touchUpInside) + let rightItem = UIBarButtonItem(customView: callBtn) + rightItem.tintColor = UIColor.black + navigationItem.rightBarButtonItem = rightItem + + setupUI() + } + + @objc func backBtnClick() { + navigationController?.popViewController(animated: true) + } + + @objc func showCallVC() { + var type: TUICallingType = .video + if callType == .audio { + type = .audio + } + + var userIds:[String] = [] + for item in addedV2TIMUserFullInfo { + userIds.append(item.userID) + } + + guard userIds.count > 0 else { + self.view.makeToast(CallingLocalize("Demo.TRTC.calling.tips")) + return + } + + TUICalling.shareInstance().call(userIDs: userIds, type: type) + } + + deinit { + debugPrint("deinit \(self)") + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationController?.setNavigationBarHidden(false, animated: false) + hiddenNoMembersImg(isHidden: false) + } + + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + navigationController?.setNavigationBarHidden(false, animated: false) + } + + var lastNetworkQualityCallTime: Date? +} + +extension TRTCGroupCallingContactViewController { + + func setupUI() { + constructViewHierarchy() + activateConstraints() + bindInteraction() + ToastManager.shared.position = .bottom + hiddenNoMembersImg(isHidden: false) + addedTable.reloadData() + } + + func constructViewHierarchy() { + view.addSubview(addedTableTitle) + view.addSubview(addedTable) + view.addSubview(noMembersTip) + view.addSubview(callingContactView) + } + + func activateConstraints() { + addedTableTitle.snp.makeConstraints { (make) in + make.topMargin.equalTo(view).offset(20) + make.leading.equalTo(view).offset(20) + make.trailing.equalTo(-20) + make.height.equalTo(20) + } + addedTable.snp.makeConstraints { (make) in + make.top.equalTo(addedTableTitle.snp.bottom).offset(10) + make.leading.trailing.equalTo(view) + make.height.equalTo(200) + } + noMembersTip.snp.makeConstraints { (make) in + make.top.equalTo(addedTable.bounds.size.height/3.0) + make.leading.equalTo(addedTable.bounds.size.width*154.0/375) + make.trailing.equalTo(-addedTable.bounds.size.width*154.0/375) + make.height.equalTo(addedTable.bounds.size.width*67.0/375) + } + callingContactView.snp.makeConstraints { (make) in + make.top.equalTo(addedTable.snp.bottom) + make.left.right.equalTo(view) + make.bottomMargin.equalTo(view) + } + } + + func bindInteraction() { + // 设置选择通话用户结束后的交互逻辑 + selectedFinished = { [weak self] users in + guard let `self` = self else {return} + self.deleteAddedUser(users: users) + } + } + + func deleteAddedUser(users: [V2TIMUserFullInfo]) { + guard users.count > 0 else { return } + let waitingAddUser: V2TIMUserFullInfo = users.first! + var waitingDeleteUser : Int = -1 + + for (index, item) in addedV2TIMUserFullInfo.enumerated() { + if (waitingAddUser.userID == item.userID) { + waitingDeleteUser = index + } + } + + guard waitingDeleteUser != -1 else { return } + addedV2TIMUserFullInfo.remove(at: waitingDeleteUser) + addedTable.reloadData() + } + + func adduser(users: [V2TIMUserFullInfo]) { + guard users.count > 0 else { return } + let waitingAddUser: V2TIMUserFullInfo = users.first! + var addedFlag = false + + for _ in addedV2TIMUserFullInfo.filter({$0.userID == waitingAddUser.userID}) { + addedFlag = true + } + + guard addedFlag == false else { return } + addedV2TIMUserFullInfo.append(waitingAddUser) + addedTable.reloadData() + } + + @objc func hiddenNoMembersImg(isHidden: Bool) { + noMembersTip.isHidden = isHidden + } +} + +extension TRTCGroupCallingContactViewController: UITableViewDelegate, UITableViewDataSource { + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return addedV2TIMUserFullInfo.count + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "CallingSelectUserTableViewCell") as! CallingSelectUserTableViewCell + cell.selectionStyle = .none + let V2TIMUserFullInfo = addedV2TIMUserFullInfo[indexPath.row] + + cell.config(model: V2TIMUserFullInfo, type: .delete, selected: false) { [weak self] in + guard let `self` = self else { return } + + if let finish = self.selectedFinished { + finish([V2TIMUserFullInfo]) + } + } + + return cell + } + + public func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + view.tintColor = UIColor.clear + let header = view as! UITableViewHeaderFooterView + header.textLabel?.textColor = UIColor.white + } + + public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 60 + } + + public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + return true + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Info.plist b/iOS/TUIKit/TUICalling/Example/App/Info.plist new file mode 100644 index 000000000..2a1f53426 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Info.plist @@ -0,0 +1,74 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSCameraUsageDescription + CallingApp需要访问你的相机权限,开启后录制的视频才会有画面 + NSMicrophoneUsageDescription + CallingApp需要访问你的麦克风权限,开启后录制的视频才会有声音 + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + audio + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/iOS/TUIKit/TUICalling/Example/App/LaunchImage@2x.png b/iOS/TUIKit/TUICalling/Example/App/LaunchImage@2x.png new file mode 100644 index 000000000..35089ff54 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/LaunchImage@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/LaunchImage@3x.png b/iOS/TUIKit/TUICalling/Example/App/LaunchImage@3x.png new file mode 100644 index 000000000..0045bf7ed Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/LaunchImage@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/LaunchImage_en@2x.png b/iOS/TUIKit/TUICalling/Example/App/LaunchImage_en@2x.png new file mode 100644 index 000000000..5f93be83b Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/LaunchImage_en@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/LaunchImage_en@3x.png b/iOS/TUIKit/TUICalling/Example/App/LaunchImage_en@3x.png new file mode 100644 index 000000000..5ec52c071 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/LaunchImage_en@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/model/MainMenuItemModel.swift b/iOS/TUIKit/TUICalling/Example/App/Main/model/MainMenuItemModel.swift new file mode 100644 index 000000000..4b14e353d --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Main/model/MainMenuItemModel.swift @@ -0,0 +1,25 @@ +// +// MainMenuItemModel.swift +// TRTCScene +// +// Created by adams on 2021/5/11. +// +import UIKit + +struct MainMenuItemModel { + let imageName: String + let title: String + let content: String + let selectHandle: () -> Void + + var iconImage: UIImage? { + UIImage.init(named: imageName) + } + + init(imageName: String, title: String, content: String, selectHandle: @escaping () -> Void) { + self.imageName = imageName + self.title = title + self.content = content + self.selectHandle = selectHandle + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/Contents.json b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_audiocall.imageset/Contents.json b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_audiocall.imageset/Contents.json new file mode 100644 index 000000000..c42d78655 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_audiocall.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "main_home_audiocall.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "main_home_audiocall@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "main_home_audiocall@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_audiocall.imageset/main_home_audiocall.png b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_audiocall.imageset/main_home_audiocall.png new file mode 100644 index 000000000..62cc53d23 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_audiocall.imageset/main_home_audiocall.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_audiocall.imageset/main_home_audiocall@2x.png b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_audiocall.imageset/main_home_audiocall@2x.png new file mode 100644 index 000000000..c9bff7328 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_audiocall.imageset/main_home_audiocall@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_audiocall.imageset/main_home_audiocall@3x.png b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_audiocall.imageset/main_home_audiocall@3x.png new file mode 100644 index 000000000..2ba2cf1dc Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_audiocall.imageset/main_home_audiocall@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_videocall.imageset/Contents.json b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_videocall.imageset/Contents.json new file mode 100644 index 000000000..f90cbefa3 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_videocall.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "main_home_videocall.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "main_home_videocall@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "main_home_videocall@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_videocall.imageset/main_home_videocall.png b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_videocall.imageset/main_home_videocall.png new file mode 100644 index 000000000..efe21e14c Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_videocall.imageset/main_home_videocall.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_videocall.imageset/main_home_videocall@2x.png b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_videocall.imageset/main_home_videocall@2x.png new file mode 100644 index 000000000..a6dd3e45e Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_videocall.imageset/main_home_videocall@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_videocall.imageset/main_home_videocall@3x.png b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_videocall.imageset/main_home_videocall@3x.png new file mode 100644 index 000000000..b2e8a3362 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_home_videocall.imageset/main_home_videocall@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_mine_about_back.imageset/Contents.json b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_mine_about_back.imageset/Contents.json new file mode 100644 index 000000000..ac3d45c4a --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_mine_about_back.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "main_mine_about_back.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "main_mine_about_back@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "main_mine_about_back@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_mine_about_back.imageset/main_mine_about_back.png b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_mine_about_back.imageset/main_mine_about_back.png new file mode 100644 index 000000000..c1b18270d Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_mine_about_back.imageset/main_mine_about_back.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_mine_about_back.imageset/main_mine_about_back@2x.png b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_mine_about_back.imageset/main_mine_about_back@2x.png new file mode 100644 index 000000000..e0d845d1a Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_mine_about_back.imageset/main_mine_about_back@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_mine_about_back.imageset/main_mine_about_back@3x.png b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_mine_about_back.imageset/main_mine_about_back@3x.png new file mode 100644 index 000000000..ed992662c Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/App/Main/resource/assets/MainAssets.xcassets/main_mine_about_back.imageset/main_mine_about_back@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/localized/MainLocalized.swift b/iOS/TUIKit/TUICalling/Example/App/Main/resource/localized/MainLocalized.swift new file mode 100644 index 000000000..115ea32eb --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Main/resource/localized/MainLocalized.swift @@ -0,0 +1,14 @@ +// +// MainLocalized.strings +// TUICallingApp +// +// Created by noah on 2021/12/27. +// + +import Foundation + +let MainLocalizeTableName = "MainLocalized" + +func MainLocalize(_ key: String) -> String { + return localizeFromTable(key: key, table: MainLocalizeTableName) +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/localized/en.lproj/MainLocalized.strings b/iOS/TUIKit/TUICalling/Example/App/Main/resource/localized/en.lproj/MainLocalized.strings new file mode 100644 index 000000000..0d34cf803 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Main/resource/localized/en.lproj/MainLocalized.strings @@ -0,0 +1,22 @@ +// +// MainLocalized.strings +// TUICallingApp +// +// Created by noah on 2021/12/27. +// + +"Audio Call" = "Voice Call"; +"Video Call" = "Video Call"; +"App.PortalViewController.highqualityspeech" = "48 kHz sample rate, uninterrupted even when packet loss reaches 60%, and leading audio processing technologies to eliminate echo and feedback"; +"App.PortalViewController.supports1080PUHDvideo" = "1080p UHD videos, uninterrupted even when packet loss reaches 50%, and built-in beauty filters to ensure premium video call experience"; +"App.PortalViewController.loginimfailed" = "Failed to log in to IM"; +"App.PortalViewController.videocalling" = "Video Call"; +"App.PortalViewController.audiocalling" = "Voice Call"; +"App.PortalViewController.groupvideocalling" = "Group Video Call"; +"App.PortalViewController.groupaudiocalling" = "Group Voice Call"; +"App.PortalViewController.areyousureloginout" = "Are you sure you want to log out?"; +"App.PortalViewController.cancel" = "Cancel"; +"App.PortalViewController.determine" = "Yes"; +"Demo.TRTC.Portal.Home.audiocalldesc" = "Voice call still available with 70% packet loss"; +"Demo.TRTC.Portal.Home.videocalldesc" = "Video call still available with 50% packet loss"; +"Demo.TRTC.Portal.Home.logout" = "Log out"; diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/resource/localized/zh-Hans.lproj/MainLocalized.strings b/iOS/TUIKit/TUICalling/Example/App/Main/resource/localized/zh-Hans.lproj/MainLocalized.strings new file mode 100644 index 000000000..4e8c09414 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Main/resource/localized/zh-Hans.lproj/MainLocalized.strings @@ -0,0 +1,22 @@ +// +// MainLocalized.strings +// TUICallingApp +// +// Created by noah on 2021/12/27. +// + +"Audio Call" = "语音通话"; +"Video Call" = "视频通话"; +"App.PortalViewController.highqualityspeech" = "48kHz高音质语音,60%丢包可正常语音,领先行业的3A处理,杜绝回声和啸叫"; +"App.PortalViewController.supports1080PUHDvideo" = "支持1080P超清视频,50%丢包率可正常视频,自备美颜特效,带来高品质视频通话体验"; +"App.PortalViewController.loginimfailed" = "登录IM失败"; +"App.PortalViewController.videocalling" = "视频通话"; +"App.PortalViewController.audiocalling" = "语音通话"; +"App.PortalViewController.groupvideocalling" = "多人视频通话"; +"App.PortalViewController.groupaudiocalling" = "多人语音通话"; +"App.PortalViewController.areyousureloginout" = "确定要退出登录吗?"; +"App.PortalViewController.cancel" = "取消"; +"App.PortalViewController.determine" = "确定"; +"Demo.TRTC.Portal.Home.audiocalldesc" = "丢包率70%仍可正常语音通话"; +"Demo.TRTC.Portal.Home.videocalldesc" = "丢包率50%仍可正常视频通话"; +"Demo.TRTC.Portal.Home.logout" = "退出登录"; diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/ui/ColorMacro.h b/iOS/TUIKit/TUICalling/Example/App/Main/ui/ColorMacro.h new file mode 100755 index 000000000..b9a81be60 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Main/ui/ColorMacro.h @@ -0,0 +1,64 @@ +// +// ColorMarcro.h +// CommonLibrary +// +// Created by Alexi on 13-10-23. +// Copyright (c) 2013年 ywchen. All rights reserved. +// + +#ifndef CommonLibrary_ColorMarcro_h +#define CommonLibrary_ColorMarcro_h + + +// 取色值相关的方法 +#define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \ +green:((float)((rgbValue & 0xFF00) >> 8))/255.0 \ +blue:((float)(rgbValue & 0xFF))/255.0 \ +alpha:1.0] + +#define RGB(r,g,b) [UIColor colorWithRed:(r)/255.f \ + green:(g)/255.f \ + blue:(b)/255.f \ + alpha:1.f] + +#define RGBA(r,g,b,a) [UIColor colorWithRed:(r)/255.f \ + green:(g)/255.f \ + blue:(b)/255.f \ + alpha:(a)] + +#define RGBOF(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \ + green:((float)((rgbValue & 0xFF00) >> 8))/255.0 \ + blue:((float)(rgbValue & 0xFF))/255.0 \ + alpha:1.0] + +#define RGBA_OF(rgbValue) [UIColor colorWithRed:((float)(((rgbValue) & 0xFF000000) >> 24))/255.0 \ + green:((float)(((rgbValue) & 0x00FF0000) >> 16))/255.0 \ + blue:((float)(rgbValue & 0x0000FF00) >> 8)/255.0 \ + alpha:((float)(rgbValue & 0x000000FF))/255.0] + +#define RGBAOF(v, a) [UIColor colorWithRed:((float)(((v) & 0xFF0000) >> 16))/255.0 \ + green:((float)(((v) & 0x00FF00) >> 8))/255.0 \ + blue:((float)(v & 0x0000FF))/255.0 \ + alpha:a] + + +// 定义通用颜色 +#define kBlackColor [UIColor blackColor] +#define kDarkGrayColor [UIColor darkGrayColor] +#define kLightGrayColor [UIColor lightGrayColor] +#define kWhiteColor [UIColor whiteColor] +#define kGrayColor [UIColor grayColor] +#define kRedColor [UIColor redColor] +#define kGreenColor [UIColor greenColor] +#define kBlueColor [UIColor blueColor] +#define kCyanColor [UIColor cyanColor] +#define kYellowColor [UIColor yellowColor] +#define kMagentaColor [UIColor magentaColor] +#define kOrangeColor [UIColor orangeColor] +#define kPurpleColor [UIColor purpleColor] +#define kClearColor [UIColor clearColor] + +#define kRandomFlatColor [UIColor randomFlatColor] + + +#endif diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/ui/MainCollectionViewCell.swift b/iOS/TUIKit/TUICalling/Example/App/Main/ui/MainCollectionViewCell.swift new file mode 100644 index 000000000..9d3172865 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Main/ui/MainCollectionViewCell.swift @@ -0,0 +1,116 @@ +// +// MainCollectionViewCell.swift +// TRTCScene +// +// Created by adams on 2021/5/11. +// + +import UIKit + +class MainCollectionViewCell: UICollectionViewCell { + static let reuseIdentifier = "MainCollectionViewCell_Reuse_Identifier" + + private let containerView: UIView = { + let view = UIView.init(frame: .zero) + view.backgroundColor = .white + return view + }() + + private let iconImageView: UIImageView = { + let imageView = UIImageView.init(frame: .zero) + imageView.contentMode = .scaleAspectFit + return imageView + }() + + private let titleLabel: UILabel = { + let label = UILabel.init(frame: .zero) + label.font = UIFont.init(name: "PingFangSC-Medium", size: 18) + label.textColor = UIColor.init("333333") + label.textAlignment = .center + label.numberOfLines = 2 + label.adjustsFontSizeToFitWidth = true + label.minimumScaleFactor = 0.5 + return label + }() + + private let descLabel: UILabel = { + let label = UILabel.init(frame: .zero) + label.font = UIFont.init(name: "PingFangSC-Regular", size: 14) + label.textColor = UIColor.init("666666") + label.textAlignment = .left + label.numberOfLines = 0 + label.adjustsFontSizeToFitWidth = true + label.minimumScaleFactor = 0.5 + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setupViewHierarchy() + setupViewConstraints() + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + roundedRect(rect: containerView.frame, byRoundingCorners: .allCorners, cornerRadii: CGSize.init(width: 10, height: 10)) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension MainCollectionViewCell { + + private func setupViewHierarchy() { + contentView.addSubview(containerView) + containerView.addSubview(iconImageView) + containerView.addSubview(titleLabel) + containerView.addSubview(descLabel) + } + + private func setupViewConstraints() { + containerView.snp.makeConstraints { (make) in + make.edges.equalToSuperview() + } + + iconImageView.snp.makeConstraints { (make) in + make.left.equalToSuperview() + make.top.equalToSuperview() + make.bottom.equalToSuperview() + make.width.equalTo(iconImageView.snp.height).multipliedBy(170.0/136.0) + } + + titleLabel.snp.makeConstraints { (make) in + make.left.equalTo(iconImageView.snp.right).offset(12) + make.right.lessThanOrEqualToSuperview().offset(-20) + make.top.equalTo(36) + make.bottom.equalTo(descLabel.snp.top).offset(-4) + } + + descLabel.snp.makeConstraints { (make) in + make.left.equalTo(titleLabel.snp.left) + make.right.lessThanOrEqualToSuperview().offset(-20) + make.bottom.lessThanOrEqualToSuperview().offset(-8) + } + } + +} + +extension MainCollectionViewCell { + + public func config(_ model: MainMenuItemModel) { + if model.imageName.hasPrefix("http") { + if let imageURL = URL.init(string: model.imageName) { + iconImageView.kf.setImage(with: .network(imageURL)) + } + } + else { + iconImageView.image = model.iconImage + } + titleLabel.text = model.title + descLabel.text = model.content + } + +} diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/ui/MainMenuCell.h b/iOS/TUIKit/TUICalling/Example/App/Main/ui/MainMenuCell.h new file mode 100644 index 000000000..b3052f4b5 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Main/ui/MainMenuCell.h @@ -0,0 +1,37 @@ +// +// MainMenuCell.h +// TRTCDemo +// +// Created by LiuXiaoya on 2020/1/13. +// Copyright © 2020 rushanting. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class MainMenuItem; + +@interface MainMenuCell : UICollectionViewCell + +@property (nonatomic, strong) MainMenuItem *item; + +@end + +@interface MainMenuItem : NSObject + +@property (strong, nonatomic, readonly) NSString *title; +@property (strong, nonatomic, readonly) NSString *content; +@property (strong, nonatomic, readonly) UIImage *icon; +@property (copy, nonatomic, readonly) void (^selectBlock)(void); + +- (instancetype)initWithIcon:(UIImage *)image + title:(NSString *)title + content:(NSString *)content + onSelect:(void(^)(void))selectBlock NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/ui/MainMenuCell.m b/iOS/TUIKit/TUICalling/Example/App/Main/ui/MainMenuCell.m new file mode 100644 index 000000000..aef195b14 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Main/ui/MainMenuCell.m @@ -0,0 +1,53 @@ +// +// MainMenuCell.m +// TRTCDemo +// +// Created by LiuXiaoya on 2020/1/13. +// Copyright © 2020 rushanting. All rights reserved. +// + +#import "MainMenuCell.h" + +@interface MainMenuCell() + +@property (weak, nonatomic) IBOutlet UIImageView *iconView; +@property (weak, nonatomic) IBOutlet UILabel *titleLabel; +@property (weak, nonatomic) IBOutlet UILabel *descLabel; + +@end + +@implementation MainMenuCell + +- (void)setItem:(MainMenuItem *)item { + self.iconView.image = item.icon; + self.titleLabel.text = item.title; + self.descLabel.text = item.content; +} + +@end + +@interface MainMenuItem() + +@property (strong, nonatomic) NSString *title; +@property (strong, nonatomic) NSString *content; +@property (strong, nonatomic) UIImage *icon; +@property (copy, nonatomic) void (^selectBlock)(void); + +@end + +@implementation MainMenuItem + +- (instancetype)initWithIcon:(UIImage *)image + title:(NSString *)title + content:(NSString *)content + onSelect:(void(^)(void))selectBlock { + if (self = [super init]) { + self.icon = image; + self.title = title; + self.content = content; + self.selectBlock = selectBlock; + } + return self; +} + +@end diff --git a/iOS/TUIKit/TUICalling/Example/App/Main/ui/MainViewController.swift b/iOS/TUIKit/TUICalling/Example/App/Main/ui/MainViewController.swift new file mode 100644 index 000000000..d94965766 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/Main/ui/MainViewController.swift @@ -0,0 +1,196 @@ +// +// MainViewController.swift +// TUICallingApp +// +// Created by noah on 2021/12/27. +// + +import UIKit +import Toast_Swift +import ImSDK_Plus +import TUICalling +import TUICore + +class MainViewController: UIViewController { + + lazy var collectionView: UICollectionView = { + let flowLayout = UICollectionViewFlowLayout() + flowLayout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) + flowLayout.itemSize = CGSize(width: view.bounds.width - 40, height: 144) + flowLayout.minimumLineSpacing = 16 + flowLayout.minimumInteritemSpacing = 16 + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) + collectionView.register(MainCollectionViewCell.self, forCellWithReuseIdentifier: MainCollectionViewCell.reuseIdentifier) + collectionView.backgroundColor = UIColor.clear + collectionView.delegate = self + collectionView.dataSource = self + return collectionView + }() + + lazy var backBtn: UIButton = { + let backBtn = UIButton(type: .custom) + backBtn.setTitle(MainLocalize("Demo.TRTC.Portal.Home.logout"), for: .normal) + backBtn.setTitleColor(UIColor.black, for: .normal) + backBtn.titleLabel?.font = UIFont(name: "PingFangSC-Regular", size: 16) + backBtn.sizeToFit() + return backBtn + }() + + let callingContactVC = TRTCCallingContactViewController() + + var logFilesArray: [String] = [] + lazy var mainMenuItems: [MainMenuItemModel] = { + return [ + MainMenuItemModel(imageName: "main_home_audiocall", + title: MainLocalize("Audio Call"), + content: MainLocalize("Demo.TRTC.Portal.Home.audiocalldesc"), + selectHandle: { [weak self] in + guard let `self` = self else { return } + self.gotoAudioCallView() + }), + MainMenuItemModel(imageName: "main_home_videocall", + title: MainLocalize("Video Call"), + content: MainLocalize("Demo.TRTC.Portal.Home.videocalldesc"), + selectHandle: { [weak self] in + guard let `self` = self else { return } + self.gotoVideoCallView() + }), + MainMenuItemModel(imageName: "main_home_audiocall", + title: MainLocalize("App.PortalViewController.groupaudiocalling"), + content: MainLocalize("Demo.TRTC.Portal.Home.audiocalldesc"), + selectHandle: { [weak self] in + guard let `self` = self else { return } + self.gotoGroupAudioCallView() + }), + MainMenuItemModel(imageName: "main_home_videocall", + title: MainLocalize("App.PortalViewController.groupvideocalling"), + content: MainLocalize("Demo.TRTC.Portal.Home.videocalldesc"), + selectHandle: { [weak self] in + guard let `self` = self else { return } + self.gotoGroupVideoCallView() + }), + ] + }() + + override func viewDidLoad() { + super.viewDidLoad() + setupNaviBar() + backBtn.addTarget(self, action: #selector(logout(sender:)), for: .touchUpInside) + view.addSubview(collectionView) + collectionView.snp.makeConstraints { (make) in + make.edges.equalToSuperview() + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + setupToast() + navigationController?.setNavigationBarHidden(false, animated: false) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + if #available(iOS 13.0, *) { + return .darkContent + } + return .default + } +} + +extension MainViewController { + + private func setupNaviBar() { + view.backgroundColor = UIColor("F4F5F9") + navigationController?.navigationBar.topItem?.title = "TRTC" + navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor:UIColor.black, + NSAttributedString.Key.font:UIFont(name: "PingFangSC-Semibold", size: 18)!] + navigationController?.navigationBar.barTintColor = UIColor.white + navigationController?.navigationBar.isTranslucent = false + let item = UIBarButtonItem(customView: backBtn) + navigationItem.rightBarButtonItems = [item] + } +} + +extension MainViewController { + func gotoAudioCallView() { + let audioCallVC = TRTCCallingContactViewController() + audioCallVC.callType = .audio + audioCallVC.title = MainLocalize("App.PortalViewController.audiocalling") + audioCallVC.hidesBottomBarWhenPushed = true + navigationController?.pushViewController(audioCallVC, animated: true) + } + + func gotoVideoCallView() { + let videoCallVC = TRTCCallingContactViewController() + videoCallVC.callType = .video + videoCallVC.title = MainLocalize("App.PortalViewController.videocalling") + videoCallVC.hidesBottomBarWhenPushed = true + navigationController?.pushViewController(videoCallVC, animated: true) + } + + func gotoGroupAudioCallView() { + let groupAudioCallVC = TRTCGroupCallingContactViewController() + groupAudioCallVC.callType = .audio + groupAudioCallVC.title = MainLocalize("App.PortalViewController.groupaudiocalling") + groupAudioCallVC.hidesBottomBarWhenPushed = true + navigationController?.pushViewController(groupAudioCallVC, animated: true) + } + + func gotoGroupVideoCallView() { + let groupVideoCallVC = TRTCGroupCallingContactViewController() + groupVideoCallVC.callType = .video + groupVideoCallVC.title = MainLocalize("App.PortalViewController.groupvideocalling") + groupVideoCallVC.hidesBottomBarWhenPushed = true + navigationController?.pushViewController(groupVideoCallVC, animated: true) + } +} + +extension MainViewController { + @objc private func logout(sender: UIButton) { + let alertVC = UIAlertController(title: MainLocalize("App.PortalViewController.areyousureloginout"), message: nil, preferredStyle: .alert) + let cancelAction = UIAlertAction(title: MainLocalize("App.PortalViewController.cancel"), style: .cancel, handler: nil) + let sureAction = UIAlertAction(title: MainLocalize("App.PortalViewController.determine"), style: .default) { (action) in + ProfileManager.shared.removeLoginCache() + AppUtils.shared.appDelegate.showLoginViewController() + TUILogin.logout { + } fail: { _, _ in + } + + V2TIMManager.sharedInstance()?.logout({ + + }, fail: { (errCode, errMsg) in + + }) + } + alertVC.addAction(cancelAction) + alertVC.addAction(sureAction) + present(alertVC, animated: true, completion: nil) + } +} + +extension MainViewController { + @objc func setupToast() { + ToastManager.shared.position = .bottom + } +} + +extension MainViewController: UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + mainMenuItems[indexPath.row].selectHandle() + } +} + +extension MainViewController: UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return mainMenuItems.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let model = mainMenuItems[indexPath.item] + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MainCollectionViewCell.reuseIdentifier, + for: indexPath) as! MainCollectionViewCell + cell.config(model) + return cell + } +} diff --git a/iOS/TUIKit/TUICalling/Example/App/SceneDelegate.swift b/iOS/TUIKit/TUICalling/Example/App/SceneDelegate.swift new file mode 100644 index 000000000..8a5ab311d --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/SceneDelegate.swift @@ -0,0 +1,65 @@ +// +// SceneDelegate.swift +// TRTCCalling +// +// Created by adams on 2021/5/7. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let windowScene = (scene as? UIWindowScene) else { return } + self.window = UIWindow.init(windowScene: windowScene) + self.window?.backgroundColor = UIColor.white + + let loginVC = TRTCLoginViewController.init() + let nav = UINavigationController.init(rootViewController: loginVC) + self.window?.rootViewController = nav + self.window?.makeKeyAndVisible() + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + + // 清除当前 APP 的推送通知 + UNUserNotificationCenter.current().removeAllDeliveredNotifications() + UNUserNotificationCenter.current().removeAllPendingNotificationRequests() + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + static func getCurrentWindow() -> UIWindow? { + return UIApplication.shared.windows.first + } + +} + diff --git a/iOS/TUIKit/TUICalling/Example/App/ViewController.swift b/iOS/TUIKit/TUICalling/Example/App/ViewController.swift new file mode 100644 index 000000000..d706fc748 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/ViewController.swift @@ -0,0 +1,18 @@ +// +// ViewController.swift +// TRTCCalling +// +// Created by adams on 2021/5/7. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + + } +} + diff --git a/iOS/TUIKit/TUICalling/Example/App/en.lproj/InfoPlist.strings b/iOS/TUIKit/TUICalling/Example/App/en.lproj/InfoPlist.strings new file mode 100644 index 000000000..690ba5a61 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/en.lproj/InfoPlist.strings @@ -0,0 +1,9 @@ +/* + InfoPlist.strings + TUICallingApp + + Created by adams on 2021/6/4. + +*/ + +"UILaunchStoryboardName" = "LaunchScreen_en"; diff --git a/iOS/TUIKit/TUICalling/Example/App/zh-Hans.lproj/InfoPlist.strings b/iOS/TUIKit/TUICalling/Example/App/zh-Hans.lproj/InfoPlist.strings new file mode 100644 index 000000000..629914de3 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/zh-Hans.lproj/InfoPlist.strings @@ -0,0 +1,9 @@ +/* + InfoPlist.strings + TUICallingApp + + Created by adams on 2021/6/4. + +*/ + +"UILaunchStoryboardName" = "LaunchScreen"; diff --git a/iOS/TUIKit/TUICalling/Example/App/zh-Hans.lproj/LaunchScreen.strings b/iOS/TUIKit/TUICalling/Example/App/zh-Hans.lproj/LaunchScreen.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/zh-Hans.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/iOS/TUIKit/TUICalling/Example/App/zh-Hans.lproj/Main.strings b/iOS/TUIKit/TUICalling/Example/App/zh-Hans.lproj/Main.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/App/zh-Hans.lproj/Main.strings @@ -0,0 +1 @@ + diff --git a/iOS/TUIKit/TUICalling/Example/Debug/GenerateTestUserSig.swift b/iOS/TUIKit/TUICalling/Example/Debug/GenerateTestUserSig.swift new file mode 100644 index 000000000..0b9e85cac --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/Debug/GenerateTestUserSig.swift @@ -0,0 +1,122 @@ +// +// GenerateTestUserSig.swift +// TRTCVoiceRoomApp +// +// Created by abyyxwang on 2021/5/7. +// + +import Foundation +import CommonCrypto +import zlib + +/** + * 腾讯云 SDKAppId,需要替换为您自己账号下的 SDKAppId。 + * + * 进入腾讯云云通信[控制台](https://console.cloud.tencent.com/avc) 创建应用,即可看到 SDKAppId, + * 它是腾讯云用于区分客户的唯一标识。 + */ +let SDKAPPID: Int = 1400590001; + +/** + * 签名过期时间,建议不要设置的过短 + * + * 时间单位:秒 + * 默认时间:7 x 24 x 60 x 60 = 604800 = 7 天 + */ +let EXPIRETIME: Int = 604800; + +/** + * 计算签名用的加密密钥,获取步骤如下: + * + * step1. 进入腾讯云云通信[控制台](https://console.cloud.tencent.com/avc) ,如果还没有应用就创建一个, + * step2. 单击“应用配置”进入基础配置页面,并进一步找到“帐号体系集成”部分。 + * step3. 点击“查看密钥”按钮,就可以看到计算 UserSig 使用的加密的密钥了,请将其拷贝并复制到如下的变量中 + * + * 注意:该方案仅适用于调试Demo,正式上线前请将 UserSig 计算代码和密钥迁移到您的后台服务器上,以避免加密密钥泄露导致的流量盗用。 + * 文档:https://cloud.tencent.com/document/product/269/32688#Server + */ +let SECRETKEY = "33c0dcda7a6177975500383473d7b7ac63ae6b3167afb3644e8aa1b267aeb377"; + +class GenerateTestUserSig { + + class func genTestUserSig(identifier: String) -> String { + let current = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970; + let TLSTime: CLong = CLong(floor(current)) + var obj: [String: Any] = [ + "TLS.ver": "2.0", + "TLS.identifier": identifier, + "TLS.sdkappid": SDKAPPID, + "TLS.expire": EXPIRETIME, + "TLS.time": TLSTime + ] + let keyOrder = [ + "TLS.identifier", + "TLS.sdkappid", + "TLS.time", + "TLS.expire" + ] + var stringToSign = "" + keyOrder.forEach { (key) in + if let value = obj[key] { + stringToSign += "\(key):\(value)\n" + } + } + print("string to sign: \(stringToSign)") + let sig = hmac(stringToSign) + obj["TLS.sig"] = sig! + print("sig: \(String(describing: sig))") + guard let jsonData = try? JSONSerialization.data(withJSONObject: obj, options: .sortedKeys) else { return "" } + + let bytes = jsonData.withUnsafeBytes { (result) -> UnsafePointer in + return result.bindMemory(to: Bytef.self).baseAddress! + } + let srcLen: uLongf = uLongf(jsonData.count) + let upperBound: uLong = compressBound(srcLen) + let capacity: Int = Int(upperBound) + let dest: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: capacity) + var destLen = upperBound + let ret = compress2(dest, &destLen, bytes, srcLen, Z_BEST_SPEED) + if ret != Z_OK { + print("[Error] Compress Error \(ret), upper bound: \(upperBound)") + dest.deallocate() + return "" + } + let count = Int(destLen) + let result = self.base64URL(data: Data.init(bytesNoCopy: dest, count: count, deallocator: .free)) + return result + } + + class func hmac(_ plainText: String) -> String? { + let cKey = SECRETKEY.cString(using: String.Encoding.ascii) + let cData = plainText.cString(using: String.Encoding.ascii) + + let cKeyLen = SECRETKEY.lengthOfBytes(using: .ascii) + let cDataLen = plainText.lengthOfBytes(using: .ascii) + + var cHMAC = [CUnsignedChar].init(repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) + let pointer = cHMAC.withUnsafeMutableBufferPointer { (unsafeBufferPointer) in + return unsafeBufferPointer + } + CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), cKey!, cKeyLen, cData, cDataLen, pointer.baseAddress) + let data = Data.init(bytes: pointer.baseAddress!, count: cHMAC.count) + return data.base64EncodedString(options: []) + } + + class func base64URL(data: Data) -> String { + let result = data.base64EncodedString(options: Data.Base64EncodingOptions.init(rawValue: 0)) + var final = "" + result.forEach { (char) in + switch char { + case "+": + final += "*" + case "/": + final += "-" + case "=": + final += "_" + default: + final += "\(char)" + } + } + return final + } +} diff --git a/iOS/TUIKit/TUICalling/Example/LoginMock/AppLocalized/LoginLocalized.swift b/iOS/TUIKit/TUICalling/Example/LoginMock/AppLocalized/LoginLocalized.swift new file mode 100644 index 000000000..d7317c419 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/LoginMock/AppLocalized/LoginLocalized.swift @@ -0,0 +1,40 @@ +// +// LoginLocalized.swift +// TRTCVoiceRoomApp +// +// Created by abyyxwang on 2021/5/7. +// + +import Foundation + +//MARK: Base +func localizeFromTable(key: String, table: String) -> String { + return Bundle.main.localizedString(forKey: key, value: "", table: table) +} + +//MARK: Replace String +func localizeReplaceOneCharacter(origin: String, xxx_replace: String) -> String { + return origin.replacingOccurrences(of: "xxx", with: xxx_replace) +} + +func localizeReplaceTwoCharacter(origin: String, xxx_replace: String, yyy_replace: String) -> String { + return localizeReplaceOneCharacter(origin: origin, xxx_replace: xxx_replace).replacingOccurrences(of: "yyy", with: yyy_replace) +} + +func localizeReplaceThreeCharacter(origin: String, xxx_replace: String, yyy_replace: String, zzz_replace: String) -> String { + return localizeReplaceTwoCharacter(origin: origin, xxx_replace: xxx_replace, yyy_replace: yyy_replace).replacingOccurrences(of: "zzz", with: zzz_replace) +} + +func localizeReplaceFourCharacter(origin: String, xxx_replace: String, yyy_replace: String, zzz_replace: String, mmm_replace: String) -> String { + return localizeReplaceThreeCharacter(origin: origin, xxx_replace: xxx_replace, yyy_replace: yyy_replace, zzz_replace: zzz_replace).replacingOccurrences(of: "mmm", with: mmm_replace) +} + +func localizeReplaceFiveCharacter(origin: String, xxx_replace: String, yyy_replace: String, zzz_replace: String, mmm_replace: String, nnn_replace: String) -> String { + return localizeReplaceFourCharacter(origin: origin, xxx_replace: xxx_replace, yyy_replace: yyy_replace, zzz_replace: zzz_replace, mmm_replace: mmm_replace).replacingOccurrences(of: "nnn", with: nnn_replace) +} + +//MARK: Login +let LoginLocalizeTableName = "LoginLocalized" +func LoginLocalize(key: String) -> String { + return localizeFromTable(key: key, table: LoginLocalizeTableName) +} diff --git a/iOS/TUIKit/TUICalling/Example/LoginMock/AppLocalized/en.lproj/LoginLocalized.strings b/iOS/TUIKit/TUICalling/Example/LoginMock/AppLocalized/en.lproj/LoginLocalized.strings new file mode 100644 index 000000000..08caad229 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/LoginMock/AppLocalized/en.lproj/LoginLocalized.strings @@ -0,0 +1,19 @@ +/* + LoginLocalized.strings + TRTCVoiceRoomApp + + Created by adams on 2021/5/8. + +*/ + +"Demo.TRTC.Login.welcome" = "Welcome to Tencent Real-Time Communication"; +"V2.Live.LinkMicNew.enterphonenumber" = "Enter a User Name"; +"V2.Live.LinkMicNew.enterverificationcode" = "Enter a verification code"; +"V2.Live.LinkMicNew.getverificationcode" = "Get"; +"V2.Live.LoginMock.login" = "Log In"; +"App.PortalViewController.loginimfailed" = "Failed to log in to IM"; +"V2.Live.LinkMicNew.loginsuccess" = "Logged in"; +"Demo.TRTC.Login.regist" = "Register"; +"Demo.TRTC.Login.enterusername" = "Please enter a nickname"; +"Demo.TRTC.Login.limit20count" = "Chinese characters, letters, numbers and underscores, 2 - 20 words"; +"Demo.TRTC.Login.registsuccess" = "Registration successful"; diff --git a/iOS/TUIKit/TUICalling/Example/LoginMock/AppLocalized/zh-Hans.lproj/LoginLocalized.strings b/iOS/TUIKit/TUICalling/Example/LoginMock/AppLocalized/zh-Hans.lproj/LoginLocalized.strings new file mode 100644 index 000000000..a2b51925d --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/LoginMock/AppLocalized/zh-Hans.lproj/LoginLocalized.strings @@ -0,0 +1,19 @@ +/* + LoginLocalized.strings + TRTCVoiceRoomApp + + Created by adams on 2021/5/8. + +*/ + +"Demo.TRTC.Login.welcome" = "欢迎使用腾讯云实时音视频"; +"V2.Live.LinkMicNew.enterphonenumber" = "请输入用户名"; +"V2.Live.LinkMicNew.enterverificationcode" = "请输入验证码"; +"V2.Live.LinkMicNew.getverificationcode" = "获取验证码"; +"V2.Live.LoginMock.login" = "登录"; +"App.PortalViewController.loginimfailed" = "登录IM失败"; +"V2.Live.LinkMicNew.loginsuccess" = "登录成功"; +"Demo.TRTC.Login.regist" = "注册"; +"Demo.TRTC.Login.enterusername" = "请输入用户昵称"; +"Demo.TRTC.Login.limit20count" = "仅限中文、字母、数字和下划线,2-20个字"; +"Demo.TRTC.Login.registsuccess" = "注册成功"; diff --git a/iOS/TUIKit/TUICalling/Example/LoginMock/AppUtils.swift b/iOS/TUIKit/TUICalling/Example/LoginMock/AppUtils.swift new file mode 100644 index 000000000..89a0e62c8 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/LoginMock/AppUtils.swift @@ -0,0 +1,42 @@ +// +// appUtil.swift +// trtcScenesDemo +// +// Created by xcoderliu on 12/24/19. +// Copyright © 2019 xcoderliu. All rights reserved. +// +// 用于TRTC_SceneDemo + +import UIKit +import ImSDK_Plus + +class AppUtils: NSObject { + @objc public static let shared = AppUtils() + private override init() {} + + var deviceToken: Data? = nil + + @objc var appDelegate: AppDelegate { + return UIApplication.shared.delegate as! AppDelegate + } + + @objc var curUserId: String { + get { +#if NOT_LOGIN + return "" +#else + return V2TIMManager.sharedInstance()?.getLoginUser() ?? "" +#endif + } + } + + // MARK: - UI + @objc func showMainController() { + appDelegate.showMainViewController() + } + + @objc func alertUserTips(_ vc: UIViewController) { + // 提醒用户不要用Demo App来做违法的事情 + // 外发代码不需要提示 + } +} diff --git a/iOS/TUIKit/TUICalling/Example/LoginMock/LayoutDefine.swift b/iOS/TUIKit/TUICalling/Example/LoginMock/LayoutDefine.swift new file mode 100644 index 000000000..8b05bd4d4 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/LoginMock/LayoutDefine.swift @@ -0,0 +1,51 @@ +// +// LayoutDefine.swift +// TXLiteAVDemo +// +// Created by gg on 2021/3/15. +// Copyright © 2021 Tencent. All rights reserved. +// + +import UIKit + +public let ScreenWidth = UIScreen.main.bounds.width +public let ScreenHeight = UIScreen.main.bounds.height + +public let kDeviceIsIphoneX : Bool = { + if UIDevice.current.userInterfaceIdiom == .pad { + return false + } + let size = UIScreen.main.bounds.size + let notchValue = Int(size.width/size.height*100) + if notchValue == 216 || notchValue == 46 { + return true + } + return false +}() + +public let kDeviceSafeTopHeight : CGFloat = { + if kDeviceIsIphoneX { + return 44 + } + else { + return 20 + } +}() + +public let kDeviceSafeBottomHeight : CGFloat = { + if kDeviceIsIphoneX { + return 34 + } + else { + return 0 + } +}() + +public func convertPixel(w:CGFloat) -> CGFloat { + return w/375.0*ScreenWidth +} + +public func convertPixel(h:CGFloat) -> CGFloat { + return h/812.0*ScreenHeight +} + diff --git a/iOS/TUIKit/TUICalling/Example/LoginMock/ProfileManager_Mock.swift b/iOS/TUIKit/TUICalling/Example/LoginMock/ProfileManager_Mock.swift new file mode 100644 index 000000000..8a27a1961 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/LoginMock/ProfileManager_Mock.swift @@ -0,0 +1,181 @@ +// +// profileManager.swift +// trtcScenesDemo +// +// Created by xcoderliu on 12/23/19. +// Copyright © 2019 xcoderliu. All rights reserved. +// + +import UIKit +import Alamofire +import ImSDK_Plus +import TUICore + +@objc class LoginResultModel: NSObject, Codable { + @objc var token: String + @objc var phone: String + @objc var name: String + @objc var avatar: String + @objc var userId: String + @objc var userSig: String = "" + + public init(userID: String) { + userId = userID + token = userID + phone = userID + name = userID + + userSig = GenerateTestUserSig.genTestUserSig(identifier: userID) + avatar = "https://imgcache.qq.com/qcloud/public/static//avatar1_100.20191230.png" + super.init() + } +} + +@objc public class ProfileManager: NSObject { + @objc public static let shared = ProfileManager() + private override init() {} + + var sessionId: String = "" + @objc var curUserModel: LoginResultModel? = nil + + /// 自动登录 + /// - Parameters: + /// - success: 成功回调 + /// - failed: 失败回调 + /// - error: 错误信息 + /// - Returns:是否可以自动登录 + @objc public func autoLogin(success: @escaping ()->Void, + failed: @escaping (_ error: String)->Void) -> Bool { + let tokenKey = "com.tencent.trtcScences.demo" + if let cacheData = UserDefaults.standard.object(forKey: tokenKey) as? Data { + if let cacheUser = try? JSONDecoder().decode(LoginResultModel.self, from: cacheData) { + curUserModel = cacheUser + let fail: (_ error: String)->Void = { err in + failed(err) + UserDefaults.standard.set(nil, forKey: tokenKey) + } + login(phone: curUserModel?.userId ?? "", code: "", success: success, failed: fail, auto: true) + return true + } + } + return false + } + + /// 登录 + /// - Parameters: + /// - success: 登录成功 + /// - failed: 登录失败 + /// - error: 错误信息 + @objc public func login(phone: String, code: String, success: @escaping ()->Void, + failed: ((_ error: String) -> Void)? = nil , auto: Bool = false) { + let phoneValue = phone + if !auto { + assert(phoneValue.count > 0) + curUserModel = LoginResultModel(userID: phoneValue) + } + // cache data + let tokenKey = "com.tencent.trtcScences.demo" + do { + let cacheData = try JSONEncoder().encode(curUserModel) + UserDefaults.standard.set(cacheData, forKey: tokenKey) + } catch { + print("Save Failed") + } + success() + } + + /// 设置昵称 + /// - Parameters: + /// - name: 昵称 + /// - success: 成功回调 + /// - failed: 失败回调 + /// - error: 错误信息 + @objc public func setNickName(name: String, success: @escaping ()->Void, + failed: @escaping (_ error: String)->Void) { + let userInfo = V2TIMUserFullInfo() + userInfo.nickName = name + V2TIMManager.sharedInstance()?.setSelfInfo(userInfo, succ: { + success() + debugPrint("set profile success") + }, fail: { (code, desc) in + failed(desc ?? "") + debugPrint("set profile failed.") + }) + } + + /// IM 登录当前用户 + /// - Parameters: + /// - success: 成功 + /// - failed: 失败 + @objc func IMLogin(userSig: String, success: @escaping ()->Void, failed: @escaping (_ error: String)->Void) { + TUILogin.initWithSdkAppID(Int32(SDKAPPID)) + + guard let userID = curUserModel?.userId else { + failed("userID wrong") + return + } + let user = String(userID) + + TUILogin.login(user, userSig: userSig) { + debugPrint("login success") + // IM登录成功后,处理离线推送相关 + let config = V2TIMAPNSConfig() + // 苹果后台请求的 deviceToken + config.token = AppUtils.shared.deviceToken + V2TIMManager.sharedInstance()?.setAPNS(config, succ: { + debugPrint("setAPNS success") + }, fail: { code, msg in + debugPrint("setAPNS error code:\(code), error: \(msg ?? "nil")") + }) + + V2TIMManager.sharedInstance()?.getUsersInfo([userID], succ: { [weak self] (infos) in + guard let `self` = self else { return } + if let info = infos?.first { + self.curUserModel?.avatar = info.faceURL ?? "" + self.curUserModel?.name = info.nickName ?? "" + success() + } + else { + failed("") + } + }, fail: { (code, err) in + failed(err ?? "") + debugPrint("get user info failed, code:\(code), error: \(err ?? "nil")") + }) + } fail: { code, errorDes in + failed(errorDes ?? "") + debugPrint("login failed, code:\(code), error: \(errorDes ?? "nil")") + } + } + + @objc func curUserID() -> String? { + guard let userID = curUserModel?.userId else { + return nil + } + return userID + } + + @objc public func removeLoginCache() { + let tokenKey = "com.tencent.trtcScences.demo" + UserDefaults.standard.set(nil, forKey: tokenKey) + } + + @objc public func curUserSig() -> String { + return curUserModel?.userSig ?? "" + } + + @objc func synchronizUserInfo() { + guard let userModel = curUserModel else { + return + } + let userInfo = V2TIMUserFullInfo() + userInfo.nickName = userModel.name + userInfo.faceURL = userModel.avatar + V2TIMManager.sharedInstance()?.setSelfInfo(userInfo, succ: { + debugPrint("set profile success") + }, fail: { (code, desc) in + debugPrint("set profile failed.") + }) + } +} + diff --git a/iOS/TUIKit/TUICalling/Example/LoginMock/TRTCLoginRootView.swift b/iOS/TUIKit/TUICalling/Example/LoginMock/TRTCLoginRootView.swift new file mode 100644 index 000000000..4c5d32b9f --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/LoginMock/TRTCLoginRootView.swift @@ -0,0 +1,297 @@ +// +// TRTCLoginRootView.swift +// TXLiteAVDemo +// +// Created by gg on 2021/4/7. +// Copyright © 2021 Tencent. All rights reserved. +// + +import UIKit + +class TRTCLoginRootView: UIView { + + lazy var bgView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "login_bg")) + return imageView + }() + + lazy var titleLabel: UILabel = { + let label = UILabel(frame: .zero) + label.font = UIFont.systemFont(ofSize: 32) + label.textColor = UIColor(hex: "333333") ?? .black + label.text = .titleText + label.numberOfLines = 2 + label.adjustsFontSizeToFitWidth = true + return label + }() + + lazy var contentView: UIView = { + let view = UIView(frame: .zero) + view.backgroundColor = .white + return view + }() + + lazy var countryCodeTipsImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "detail")) + imageView.isUserInteractionEnabled = false + return imageView + }() + + lazy var phoneNumTextField: UITextField = { + let textField = createTextField(.phoneNumPlaceholderText) + textField.keyboardType = .phonePad + return textField + }() + + lazy var phoneNumBottomLine: UIView = { + let view = createSpacingLine() + return view + }() + + lazy var verifyCodeBottomLine: UIView = { + let view = createSpacingLine() + return view + }() + + lazy var loginBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setTitleColor(.white, for: .normal) + btn.setTitle(.loginText, for: .normal) + btn.adjustsImageWhenHighlighted = false + btn.setBackgroundImage(UIColor(hex: "006EFF")?.trans2Image(), for: .normal) + btn.titleLabel?.font = UIFont(name: "PingFangSC-Medium", size: 18) + btn.layer.shadowColor = UIColor(hex: "006EFF")?.cgColor ?? UIColor.blue.cgColor + btn.layer.shadowOffset = CGSize(width: 0, height: 6) + btn.layer.shadowRadius = 16 + btn.layer.shadowOpacity = 0.4 + btn.layer.masksToBounds = true + btn.isEnabled = false + return btn + }() + + private func createSpacingLine() -> UIView { + let view = UIView(frame: .zero) + view.backgroundColor = UIColor(hex: "EEEEEE") + return view + } + + private func createTextField(_ placeholder: String) -> UITextField { + let textField = UITextField(frame: .zero) + textField.backgroundColor = .white + textField.font = UIFont(name: "PingFangSC-Regular", size: 16) + textField.textColor = UIColor(hex: "333333") + textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [NSAttributedString.Key.font : UIFont(name: "PingFangSC-Regular", size: 16) ?? UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor : UIColor(hex: "BBBBBB") ?? .gray]) + textField.delegate = self + return textField + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + contentView.roundedRect(rect: contentView.bounds, byRoundingCorners: .topRight, cornerRadii: CGSize(width: 40, height: 40)) + loginBtn.layer.cornerRadius = loginBtn.frame.height * 0.5 + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + + if let current = currentTextField { + current.resignFirstResponder() + currentTextField = nil + } + UIView.animate(withDuration: 0.3) { + self.transform = .identity + } + checkLoginBtnState() + } + + weak var currentTextField: UITextField? + + public weak var rootVC: TRTCLoginViewController? + + override init(frame: CGRect) { + super.init(frame: frame) + + NotificationCenter.default.addObserver(self, selector: #selector(keyboardFrameChange(noti:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + @objc func keyboardFrameChange(noti : Notification) { + guard let info = noti.userInfo else { + return + } + guard let value = info[UIResponder.keyboardFrameEndUserInfoKey], value is CGRect else { + return + } + guard let superview = loginBtn.superview else { + return + } + let rect = value as! CGRect + let converted = superview.convert(loginBtn.frame, to: self) + if rect.intersects(converted) { + transform = CGAffineTransform(translationX: 0, y: -converted.maxY+rect.minY) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var isViewReady = false + override func didMoveToWindow() { + super.didMoveToWindow() + guard !isViewReady else { + return + } + isViewReady = true + constructViewHierarchy() // 视图层级布局 + activateConstraints() // 生成约束(此时有可能拿不到父视图正确的frame) + bindInteraction() + } + + func constructViewHierarchy() { + addSubview(bgView) + addSubview(titleLabel) + addSubview(contentView) + contentView.addSubview(phoneNumTextField) + contentView.addSubview(phoneNumBottomLine) + contentView.addSubview(loginBtn) + } + func activateConstraints() { + bgView.snp.makeConstraints { (make) in + make.top.leading.trailing.equalToSuperview() + make.height.equalTo(bgView.snp.width) + } + titleLabel.snp.makeConstraints { (make) in + make.top.equalToSuperview().offset(convertPixel(h: 86) + kDeviceSafeTopHeight) + make.leading.equalToSuperview().offset(convertPixel(w: 40)) + make.trailing.lessThanOrEqualToSuperview().offset(-convertPixel(w: 40)) + } + contentView.snp.makeConstraints { (make) in + make.top.equalTo(bgView.snp.bottom).offset(-convertPixel(h: 64)) + make.leading.trailing.bottom.equalToSuperview() + } + phoneNumTextField.snp.makeConstraints { (make) in + make.top.equalToSuperview().offset(convertPixel(h: 40)) + make.leading.equalToSuperview().offset(convertPixel(w: 40)) + make.trailing.equalToSuperview().offset(-convertPixel(w: 40)) + make.height.equalTo(convertPixel(h: 57)) + } + + phoneNumBottomLine.snp.makeConstraints { (make) in + make.bottom.leading.trailing.equalTo(phoneNumTextField) + make.height.equalTo(convertPixel(h: 1)) + } + + loginBtn.snp.makeConstraints { (make) in + make.top.equalTo(phoneNumTextField.snp.bottom).offset(convertPixel(h: 40)) + make.leading.equalToSuperview().offset(convertPixel(w: 20)) + make.trailing.equalToSuperview().offset(-convertPixel(w: 20)) + make.height.equalTo(convertPixel(h: 52)) + } + } + func bindInteraction() { + loginBtn.addTarget(self, action: #selector(loginBtnClick), for: .touchUpInside) + } + + @objc func loginBtnClick() { + if let current = currentTextField { + current.resignFirstResponder() + } + guard let phone = phoneNumTextField.text else { + return + } + rootVC?.login(phone: phone, code: "") + } + + @objc func getVerifyCodeBtnClick() { + + } +} + +extension TRTCLoginRootView: UITextFieldDelegate { + func textFieldDidBeginEditing(_ textField: UITextField) { + if let last = currentTextField { + last.resignFirstResponder() + } + currentTextField = textField + textField.becomeFirstResponder() + } + func textFieldDidEndEditing(_ textField: UITextField) { + textField.resignFirstResponder() + currentTextField = nil + UIView.animate(withDuration: 0.3) { + self.transform = .identity + } + checkLoginBtnState() + } + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + var maxCount = 11 + if textField == phoneNumTextField { + maxCount = 11 + } + else { + maxCount = 6 + } + guard let textFieldText = textField.text, + let rangeOfTextToReplace = Range(range, in: textFieldText) else { + return false + } + let substringToReplace = textFieldText[rangeOfTextToReplace] + let count = textFieldText.count - substringToReplace.count + string.count + let res = count <= maxCount + if res { + var phoneCtt = 0 + var verifyCtt = 0 + if textField == phoneNumTextField { + phoneCtt = count + verifyCtt = 6 + } + else { + phoneCtt = phoneNumTextField.text?.count ?? 0 + verifyCtt = count + } + checkLoginBtnState(phoneCtt: phoneCtt, verifyCtt: verifyCtt) + } + return res + } + + func checkLoginBtnState(phoneCtt: Int = -1, verifyCtt: Int = -1) { + var phone = 0 + var code = 0 + if phoneCtt > -1 || verifyCtt > -1 { + if phoneCtt > -1 { + phone = phoneCtt + } + else { + phone = phoneNumTextField.text?.count ?? 0 + } + if verifyCtt > -1 { + code = verifyCtt + } + else { + code = 6 + } + } + else { + phone = phoneNumTextField.text?.count ?? 0 + code = 6 + } + loginBtn.isEnabled = phone > 0 && code == 6 + } +} + +/// MARK: - internationalization string +fileprivate extension String { + static let titleText = LoginLocalize(key: "Demo.TRTC.Login.welcome") + static let phoneNumPlaceholderText = LoginLocalize(key:"V2.Live.LinkMicNew.enterphonenumber") + static let verifyCodePlaceholderText = LoginLocalize(key:"V2.Live.LinkMicNew.enterverificationcode") + static let getVerifyCodeText = LoginLocalize(key:"V2.Live.LinkMicNew.getverificationcode") + static let loginText = LoginLocalize(key:"V2.Live.LoginMock.login") +} diff --git a/iOS/TUIKit/TUICalling/Example/LoginMock/TRTCLoginViewController.swift b/iOS/TUIKit/TUICalling/Example/LoginMock/TRTCLoginViewController.swift new file mode 100644 index 000000000..ff7078fbc --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/LoginMock/TRTCLoginViewController.swift @@ -0,0 +1,113 @@ +// +// TRTCLoginViewController.swift +// TXLiteAVDemo +// +// Created by gg on 2021/4/7. +// Copyright © 2021 Tencent. All rights reserved. +// + +import Foundation +import Toast_Swift +import WebKit +import ImSDK_Plus + +class TRTCLoginViewController: UIViewController { + + let loading = UIActivityIndicatorView() + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + view.bringSubviewToFront(loading) + } + + override func viewDidLoad() { + super.viewDidLoad() + navigationController?.navigationBar.barTintColor = .white + ToastManager.shared.position = .center + view.addSubview(loading) + loading.snp.makeConstraints { (make) in + make.width.height.equalTo(40) + make.centerX.centerY.equalTo(view) + } + + /// auto login + if ProfileManager.shared.autoLogin(success: { + self.loginIM { [weak self] (success) in + guard let `self` = self else { return } + self.loading.stopAnimating() + if success { + self.loginSucc() + } + } + }, failed: { [weak self] (err) in + guard let self = self else {return} + self.loading.stopAnimating() + self.view.makeToast(err) + }) { + loading.startAnimating() + if let rootView = view as? TRTCLoginRootView { + rootView.phoneNumTextField.text = ProfileManager.shared.curUserModel?.phone ?? "" + } + } + } + + + func login(phone: String, code: String) { + loading.startAnimating() + ProfileManager.shared.login(phone: phone, code: code) { [weak self] in + guard let `self` = self else { return } + self.loading.stopAnimating() + self.loginIM { [weak self] (success) in + guard let `self` = self else { return } + if success { + self.loginSucc() + } + } + } + } + + func loginIM(complete: @escaping (_ success: Bool)->Void) { + guard let userID = ProfileManager.shared.curUserID() else { return } + let userSig = ProfileManager.shared.curUserSig() + V2TIMManager.sharedInstance() + if V2TIMManager.sharedInstance()?.getLoginUser() != userID { + ProfileManager.shared.IMLogin(userSig: userSig) { + debugPrint("IM login success.") + complete(true) + } failed: { [weak self] (error) in + guard let `self` = self else { return } + self.view.makeToast(LoginLocalize(key: "App.PortalViewController.loginimfailed")) + complete(false) + } + } + } + + func loginSucc() { + if ProfileManager.shared.curUserModel?.name.count == 0 { + showRegisterVC() + } else { + self.view.makeToast(LoginLocalize(key:"V2.Live.LinkMicNew.loginsuccess")) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + AppUtils.shared.showMainController() + } + } + } + + func showRegisterVC() { + let vc = TRTCRegisterViewController.init() + navigationController?.pushViewController(vc, animated: true) + } + + override func loadView() { + super.loadView() + let rootView = TRTCLoginRootView() + rootView.rootVC = self + view = rootView + } +} + +extension String { + static let verifySuccessStr = "verifySuccess" + static let verifyCancelStr = "verifyCancel" + static let verifyErrorStr = "verifyError" +} diff --git a/iOS/TUIKit/TUICalling/Example/LoginMock/TRTCRegisterRootView.swift b/iOS/TUIKit/TUICalling/Example/LoginMock/TRTCRegisterRootView.swift new file mode 100644 index 000000000..15d4e9601 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/LoginMock/TRTCRegisterRootView.swift @@ -0,0 +1,268 @@ +// +// TRTCRegisterRootView.swift +// TXLiteAVDemo +// +// Created by gg on 2021/4/8. +// Copyright © 2021 Tencent. All rights reserved. +// + +import UIKit +import Kingfisher + +class TRTCRegisterRootView: UIView { + + lazy var titleLabel: UILabel = { + let label = UILabel(frame: .zero) + label.font = UIFont.systemFont(ofSize: 20) + label.textColor = UIColor(hex: "333333") ?? .black + label.text = .titleText + return label + }() + + lazy var headImageView: UIImageView = { + let imageView = UIImageView(frame: .zero) + imageView.contentMode = .scaleAspectFill + imageView.layer.cornerRadius = 50 + imageView.clipsToBounds = true + return imageView + }() + + lazy var textField: UITextField = { + let textField = createTextField(.nicknamePlaceholderText) + return textField + }() + + lazy var textFieldSpacingLine: UIView = { + let view = createSpacingLine() + return view + }() + + lazy var descLabel: UILabel = { + let label = UILabel(frame: .zero) + label.font = UIFont(name: "PingFangSC-Regular", size: 16) + label.textColor = .darkGray + label.text = .descText + label.adjustsFontSizeToFitWidth = true + return label + }() + + lazy var registBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setTitleColor(.white, for: .normal) + btn.setTitle(.registText, for: .normal) + btn.adjustsImageWhenHighlighted = false + btn.setBackgroundImage(UIColor(hex: "006EFF")?.trans2Image(), for: .normal) + btn.titleLabel?.font = UIFont(name: "PingFangSC-Medium", size: 18) + btn.layer.shadowColor = UIColor(hex: "006EFF")?.cgColor ?? UIColor.blue.cgColor + btn.layer.shadowOffset = CGSize(width: 0, height: 6) + btn.layer.shadowRadius = 16 + btn.layer.shadowOpacity = 0.4 + btn.layer.masksToBounds = true + btn.isEnabled = false + return btn + }() + + private func createTextField(_ placeholder: String) -> UITextField { + let textField = UITextField(frame: .zero) + textField.backgroundColor = .white + textField.font = UIFont(name: "PingFangSC-Regular", size: 16) + textField.textColor = UIColor(hex: "333333") + textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [NSAttributedString.Key.font : UIFont(name: "PingFangSC-Regular", size: 16) ?? UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor : UIColor(hex: "BBBBBB") ?? .gray]) + textField.delegate = self + return textField + } + + private func createSpacingLine() -> UIView { + let view = UIView(frame: .zero) + view.backgroundColor = UIColor(hex: "EEEEEE") + return view + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + registBtn.layer.cornerRadius = registBtn.frame.height * 0.5 + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + + textField.resignFirstResponder() + UIView.animate(withDuration: 0.3) { + self.transform = .identity + } + checkRegistBtnState() + } + + public weak var rootVC: TRTCRegisterViewController? + + override init(frame: CGRect) { + super.init(frame: frame) + + NotificationCenter.default.addObserver(self, selector: #selector(keyboardFrameChange(noti:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + @objc func keyboardFrameChange(noti : Notification) { + guard let info = noti.userInfo else { + return + } + guard let value = info[UIResponder.keyboardFrameEndUserInfoKey], value is CGRect else { + return + } + guard let superview = textField.superview else { + return + } + let rect = value as! CGRect + let converted = superview.convert(textField.frame, to: self) + if rect.intersects(converted) { + transform = CGAffineTransform(translationX: 0, y: -converted.maxY+rect.minY) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var isViewReady = false + override func didMoveToWindow() { + super.didMoveToWindow() + guard !isViewReady else { + return + } + isViewReady = true + constructViewHierarchy() + activateConstraints() + bindInteraction() + + if let url = ProfileManager.shared.curUserModel?.avatar, url.count > 0 { + headImageView.kf.setImage(with: URL(string: url)) + } else { + let url = "https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar1.png" + ProfileManager.shared.curUserModel?.avatar = url + headImageView.kf.setImage(with: URL(string: url)) + } + } + + func constructViewHierarchy() { + addSubview(titleLabel) + addSubview(headImageView) + addSubview(textField) + addSubview(textFieldSpacingLine) + addSubview(descLabel) + addSubview(registBtn) + } + func activateConstraints() { + titleLabel.snp.makeConstraints { (make) in + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(kDeviceSafeTopHeight+10) + } + headImageView.snp.makeConstraints { (make) in + make.centerX.equalToSuperview() + make.top.equalTo(titleLabel.snp.bottom).offset(40) + make.size.equalTo(CGSize(width: 100, height: 100)) + } + textField.snp.makeConstraints { (make) in + make.top.equalTo(headImageView.snp.bottom).offset(convertPixel(h: 40)) + make.leading.equalToSuperview().offset(convertPixel(w: 40)) + make.trailing.equalToSuperview().offset(-convertPixel(w: 40)) + make.height.equalTo(convertPixel(h: 57)) + } + textFieldSpacingLine.snp.makeConstraints { (make) in + make.bottom.leading.trailing.equalTo(textField) + make.height.equalTo(1) + } + descLabel.snp.makeConstraints { (make) in + make.top.equalTo(textField.snp.bottom).offset(10) + make.leading.equalToSuperview().offset(convertPixel(w: 40)) + make.trailing.lessThanOrEqualToSuperview().offset(convertPixel(w: -40)) + } + registBtn.snp.makeConstraints { (make) in + make.top.equalTo(descLabel.snp.bottom).offset(convertPixel(h: 40)) + make.leading.equalToSuperview().offset(convertPixel(w: 20)) + make.trailing.equalToSuperview().offset(-convertPixel(w: 20)) + make.height.equalTo(convertPixel(h: 52)) + } + } + func bindInteraction() { + registBtn.addTarget(self, action: #selector(registBtnClick), for: .touchUpInside) + + let tap = UITapGestureRecognizer(target: self, action: #selector(headBtnClick)) + headImageView.addGestureRecognizer(tap) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + + guard let superview = headImageView.superview else { + return super.hitTest(point, with: event) + } + let rect = superview.convert(headImageView.frame, to: self) + if rect.contains(point) { + return headImageView + } + return super.hitTest(point, with: event) + } + + @objc func headBtnClick() { + + } + + @objc func registBtnClick() { + textField.resignFirstResponder() + guard let name = textField.text else { + return + } + ProfileManager.shared.curUserModel?.name = name + rootVC?.regist(name) + } + + func checkRegistBtnState(_ count: Int = -1) { + if count > -1 { + registBtn.isEnabled = count > 0 + } + else { + registBtn.isEnabled = false + } + } +} + +extension TRTCRegisterRootView : UITextFieldDelegate { + func textFieldDidBeginEditing(_ textField: UITextField) { + textField.becomeFirstResponder() + } + func textFieldDidEndEditing(_ textField: UITextField) { + textField.resignFirstResponder() + UIView.animate(withDuration: 0.3) { + self.transform = .identity + } + checkRegistBtnState() + } + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + let maxCount = 20 + guard let textFieldText = textField.text, + let rangeOfTextToReplace = Range(range, in: textFieldText) else { + return false + } + let substringToReplace = textFieldText[rangeOfTextToReplace] + let count = textFieldText.count - substringToReplace.count + string.count + let res = count <= maxCount + if res { + checkRegistBtnState(count) + } + return res + } +} + +/// MARK: - internationalization string +fileprivate extension String { + static let titleText = LoginLocalize(key:"Demo.TRTC.Login.regist") + static let nicknamePlaceholderText = LoginLocalize(key:"Demo.TRTC.Login.enterusername") + static let descText = LoginLocalize(key:"Demo.TRTC.Login.limit20count") + static let registText = LoginLocalize(key:"Demo.TRTC.Login.regist") +} diff --git a/iOS/TUIKit/TUICalling/Example/LoginMock/TRTCRegisterViewController.swift b/iOS/TUIKit/TUICalling/Example/LoginMock/TRTCRegisterViewController.swift new file mode 100644 index 000000000..13401af20 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/LoginMock/TRTCRegisterViewController.swift @@ -0,0 +1,70 @@ +// +// TRTCRegisterViewController.swift +// TXLiteAVDemo +// +// Created by gg on 2021/4/8. +// Copyright © 2021 Tencent. All rights reserved. +// + +import Foundation +import Toast_Swift +import TXAppBasic +import SnapKit +import UIKit +import ImSDK_Plus + +class TRTCRegisterViewController: UIViewController { + + + let loading = UIActivityIndicatorView.init(style: .large) + + override func viewDidLoad() { + super.viewDidLoad() + + ToastManager.shared.position = .center + + view.addSubview(loading) + loading.snp.makeConstraints { (make) in + make.width.height.equalTo(40) + make.centerX.centerY.equalTo(view) + } + } + + func regist(_ nickName: String) { + loading.startAnimating() + ProfileManager.shared.synchronizUserInfo() + ProfileManager.shared.setNickName(name: nickName) { [weak self] in + guard let `self` = self else { return } + self.registSuccess() + } failed: { (err) in + self.loading.stopAnimating() + self.view.makeToast(err) + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.navigationController?.popViewController(animated: true) + } + } + } + + func registSuccess() { + + self.loading.stopAnimating() + self.view.makeToast(.registSuccessText) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + //show main vc + AppUtils.shared.showMainController() + } + } + + override func loadView() { + super.loadView() + let rootView = TRTCRegisterRootView() + rootView.rootVC = self + view = rootView + } +} + +/// MARK: - internationalization string +fileprivate extension String { + static let registSuccessText = LoginLocalize(key:"Demo.TRTC.Login.registsuccess") +} + diff --git a/iOS/TUIKit/TUICalling/Example/LoginMock/UserAgreementViewController+UI.swift b/iOS/TUIKit/TUICalling/Example/LoginMock/UserAgreementViewController+UI.swift new file mode 100644 index 000000000..f3bf7e4a1 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/LoginMock/UserAgreementViewController+UI.swift @@ -0,0 +1,102 @@ +// +// UserAgreementViewController+UI.swift +// TXLiteAVDemo +// +// Created by lijie on 2020/6/23. +// Copyright © 2020 Tencent. All rights reserved. +// + +import Foundation +import SnapKit +import WebKit +import TXAppBasic + +extension UserAgreementViewController { + func setupUI() { + self.title = LoginLocalize(key:"V2.Live.LinkMicNew.termsandconditions") + + let htmlPath = Bundle.main.path(forResource: "UserProtocol", ofType: "html") + var htmlContent = "" + do { + htmlContent = try String(contentsOfFile: htmlPath ?? "") + } catch { + } + + let lineView1 = UIView() + lineView1.backgroundColor = UIColor.gray + view.addSubview(lineView1) + lineView1.snp.remakeConstraints { (make) in + make.width.equalTo(view) + make.height.equalTo(0.5) + make.leading.equalTo(0) + make.bottom.equalTo(view).offset(-50 - bottomPadding) + } + + let lineView2 = UIView() + lineView2.backgroundColor = UIColor.gray + view.addSubview(lineView2) + lineView2.snp.remakeConstraints { (make) in + make.width.equalTo(0.5) + make.height.equalTo(49) + make.leading.equalTo(view.snp.trailing).dividedBy(2) + make.top.equalTo(lineView1.snp.bottom) + } + + let agreeBtn = UIButton() + agreeBtn.setTitle(LoginLocalize(key:"V2.Live.LinkMicNew.agree"), for: .normal) + agreeBtn.setTitleColor(UIColor.init(0x0062E3), for: .normal) + view.addSubview(agreeBtn) + agreeBtn.snp.remakeConstraints { (make) in + make.width.equalTo(view).dividedBy(2) + make.height.equalTo(49) + make.leading.equalTo(0) + make.top.equalTo(lineView1.snp.bottom) + } + agreeBtn.addTarget(self, action: #selector(agreeBtnClick), for: .touchUpInside) + + + let unAgreeBtn = UIButton() + unAgreeBtn.setTitle(LoginLocalize(key:"V2.Live.LinkMicNew.disagree"), for: .normal) + unAgreeBtn.setTitleColor(UIColor.init(0x0062E3), for: .normal) + view.addSubview(unAgreeBtn) + unAgreeBtn.snp.remakeConstraints { (make) in + make.width.equalTo(view).dividedBy(2) + make.height.equalTo(49) + make.leading.equalTo(view.snp.trailing).dividedBy(2) + make.top.equalTo(lineView1.snp.bottom) + } + unAgreeBtn.addTarget(self, action: #selector(unAgreeBtnClick), for: .touchUpInside) + + + let webView = WKWebView() + webView.loadHTMLString(htmlContent, baseURL: Bundle.main.bundleURL) + view.addSubview(webView) + webView.snp.remakeConstraints { (make) in + make.top.equalTo(topPadding) + make.bottom.equalTo(lineView1.snp.top) + make.leading.equalTo(0) + make.width.equalTo(view) + } + } + + @objc func agreeBtnClick() { + agree() + } + + @objc func unAgreeBtnClick() { + unAgree() + } + + func agree() { + UserDefaults.standard.set(true, forKey: UserAgreementViewController.UserAgreeKey) + UserDefaults.standard.synchronize() + self.dismiss(animated: true, completion: completion) + } + + func unAgree() { + UserDefaults.standard.set(false, forKey: UserAgreementViewController.UserAgreeKey) + UserDefaults.standard.synchronize() + self.dismiss(animated: true, completion: completion) + } + +} diff --git a/iOS/TUIKit/TUICalling/Example/LoginMock/UserAgreementViewController.swift b/iOS/TUIKit/TUICalling/Example/LoginMock/UserAgreementViewController.swift new file mode 100644 index 000000000..271e31c8d --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/LoginMock/UserAgreementViewController.swift @@ -0,0 +1,47 @@ +// +// UserAgreementViewController.swift +// TXLiteAVDemo +// +// Created by lijie on 2020/6/23. +// Copyright © 2020 Tencent. All rights reserved. +// + +import UIKit + +class UserAgreementViewController: UIViewController { + static let UserAgreeKey = "UserAgreeKey" // 用户协议,登录或注册后第一次弹出 + + typealias Completion = () -> Void + var completion: Completion? = nil + + var topPadding: CGFloat = { + if #available(iOS 11.0, *) { + return UIApplication.shared.keyWindow!.safeAreaInsets.top + } + return 0 + }() + + var bottomPadding: CGFloat = { + if #available(iOS 11.0, *) { + return UIApplication.shared.keyWindow!.safeAreaInsets.top + } + return 0 + }() + + static func isAgree() -> Bool { + if let isAgree = UserDefaults.standard.object(forKey: UserAgreeKey) as? Bool { + return isAgree + } + return false + } + + override func viewDidLoad() { + super.viewDidLoad() + + setupUI() + } + + deinit { + debugPrint("deinit \(self)") + } +} diff --git a/iOS/TUIKit/TUICalling/Example/Podfile b/iOS/TUIKit/TUICalling/Example/Podfile new file mode 100644 index 000000000..5bbf952bc --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/Podfile @@ -0,0 +1,22 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +target 'TUICallingApp' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + pod 'TXAppBasic', :path => "./TXAppBasic/" + pod 'TXLiteAVSDK_TRTC' + pod 'Toast-Swift' + pod 'Alamofire' + pod 'Kingfisher' + pod 'SnapKit' + pod 'TUICalling', :path => "../", :subspecs => ["TRTC"] +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/iOS/TUIKit/TUICalling/Example/Podfile.lock b/iOS/TUIKit/TUICalling/Example/Podfile.lock new file mode 100644 index 000000000..bcd82ee3b --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/Podfile.lock @@ -0,0 +1,67 @@ +PODS: + - Alamofire (5.4.4) + - Kingfisher (7.1.1) + - Masonry (1.1.0) + - ReactiveObjC (3.1.1) + - SDWebImage (5.12.3): + - SDWebImage/Core (= 5.12.3) + - SDWebImage/Core (5.12.3) + - SnapKit (5.0.1) + - Toast-Swift (5.0.1) + - TUICalling/TRTC (9.5): + - Masonry + - TUICore + - TXLiteAVSDK_TRTC + - TUICore (6.0.1992): + - ReactiveObjC + - SDWebImage + - TXIMSDK_Plus_iOS (= 6.0.1975) + - TXAppBasic (1.0.0) + - TXIMSDK_Plus_iOS (6.0.1975) + - TXLiteAVSDK_TRTC (9.3.10765) + +DEPENDENCIES: + - Alamofire + - Kingfisher + - SnapKit + - Toast-Swift + - TUICalling/TRTC (from `../`) + - TXAppBasic (from `./TXAppBasic/`) + - TXLiteAVSDK_TRTC + +SPEC REPOS: + trunk: + - Alamofire + - Kingfisher + - Masonry + - ReactiveObjC + - SDWebImage + - SnapKit + - Toast-Swift + - TUICore + - TXIMSDK_Plus_iOS + - TXLiteAVSDK_TRTC + +EXTERNAL SOURCES: + TUICalling: + :path: "../" + TXAppBasic: + :path: "./TXAppBasic/" + +SPEC CHECKSUMS: + Alamofire: f3b09a368f1582ab751b3fff5460276e0d2cf5c9 + Kingfisher: f738f9c4a3415823b8e2985d10aa2d6a9f37bc4d + Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 + ReactiveObjC: 011caa393aa0383245f2dcf9bf02e86b80b36040 + SDWebImage: 53179a2dba77246efa8a9b85f5c5b21f8f43e38f + SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb + Toast-Swift: 9b6a70f28b3bf0b96c40d46c0c4b9d6639846711 + TUICalling: ec9ebbb298e6291695940ee5fe038b4484c01208 + TUICore: addee8e37417b986fad60d582555763ff153a6ae + TXAppBasic: f72fd658cb2cec20f0368158752b82226cb6cdec + TXIMSDK_Plus_iOS: b4938e8a26865ddf81fcff413236d99f6fbae39c + TXLiteAVSDK_TRTC: a383e8a7a5bee76d99e10802ceb94908b0de8e33 + +PODFILE CHECKSUM: 348a2aa7723699143c8f4bc15ab2e46a1fd136a9 + +COCOAPODS: 1.10.1 diff --git a/iOS/TUIKit/TUICalling/Example/TUICallingApp.entitlements b/iOS/TUIKit/TUICalling/Example/TUICallingApp.entitlements new file mode 100644 index 000000000..903def2af --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/TUICallingApp.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcodeproj/project.pbxproj b/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcodeproj/project.pbxproj new file mode 100644 index 000000000..581806527 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcodeproj/project.pbxproj @@ -0,0 +1,882 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 470EB6F5266A49170072071F /* LaunchImage@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 470EB6F1266A49170072071F /* LaunchImage@2x.png */; }; + 470EB6F6266A49170072071F /* LaunchImage_en@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 470EB6F2266A49170072071F /* LaunchImage_en@2x.png */; }; + 470EB6F7266A49170072071F /* LaunchImage@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 470EB6F3266A49170072071F /* LaunchImage@3x.png */; }; + 470EB6F8266A49170072071F /* LaunchImage_en@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 470EB6F4266A49170072071F /* LaunchImage_en@3x.png */; }; + 470EB6FB266A49200072071F /* LaunchScreen_en.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 470EB6F9266A49200072071F /* LaunchScreen_en.storyboard */; }; + 470EB6FE266A49380072071F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 470EB700266A49380072071F /* InfoPlist.strings */; }; + 4739C7DB2657507900CA7C45 /* CallingLocalized.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4739C7D52657507800CA7C45 /* CallingLocalized.strings */; }; + 4739C7DC2657507900CA7C45 /* CallingLocalized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4739C7D82657507800CA7C45 /* CallingLocalized.swift */; }; + 4739C7DD2657507900CA7C45 /* CallingContact.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4739C7DA2657507900CA7C45 /* CallingContact.xcassets */; }; + 4739C7DF265750A800CA7C45 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 47D9D36226521A7A0007EE1F /* Assets.xcassets */; }; + 47D9D36A26521A7A0007EE1F /* LaunchScreen.strings in Resources */ = {isa = PBXBuildFile; fileRef = 47D9D35D26521A7A0007EE1F /* LaunchScreen.strings */; }; + 47D9D36B26521A7A0007EE1F /* Main.strings in Resources */ = {isa = PBXBuildFile; fileRef = 47D9D35F26521A7A0007EE1F /* Main.strings */; }; + 47D9D36C26521A7A0007EE1F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47D9D36126521A7A0007EE1F /* ViewController.swift */; }; + 47D9D36E26521A7A0007EE1F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 47D9D36326521A7A0007EE1F /* LaunchScreen.storyboard */; }; + 47D9D36F26521A7A0007EE1F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 47D9D36526521A7A0007EE1F /* Main.storyboard */; }; + 47D9D37026521A7A0007EE1F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47D9D36726521A7A0007EE1F /* AppDelegate.swift */; }; + 47D9D37226521A7A0007EE1F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47D9D36926521A7A0007EE1F /* SceneDelegate.swift */; }; + 4F3F7CA826D65D38002912DE /* TRTCRegisterRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3F7C9A26D65D38002912DE /* TRTCRegisterRootView.swift */; }; + 4F3F7CA926D65D38002912DE /* TRTCLoginRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3F7C9B26D65D38002912DE /* TRTCLoginRootView.swift */; }; + 4F3F7CAA26D65D38002912DE /* UserAgreementViewController+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3F7C9C26D65D38002912DE /* UserAgreementViewController+UI.swift */; }; + 4F3F7CAB26D65D38002912DE /* LoginLocalized.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4F3F7C9E26D65D38002912DE /* LoginLocalized.strings */; }; + 4F3F7CAC26D65D38002912DE /* LoginLocalized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3F7CA126D65D38002912DE /* LoginLocalized.swift */; }; + 4F3F7CAD26D65D38002912DE /* TRTCLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3F7CA226D65D38002912DE /* TRTCLoginViewController.swift */; }; + 4F3F7CAE26D65D38002912DE /* ProfileManager_Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3F7CA326D65D38002912DE /* ProfileManager_Mock.swift */; }; + 4F3F7CAF26D65D38002912DE /* AppUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3F7CA426D65D38002912DE /* AppUtils.swift */; }; + 4F3F7CB026D65D38002912DE /* TRTCRegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3F7CA526D65D38002912DE /* TRTCRegisterViewController.swift */; }; + 4F3F7CB126D65D38002912DE /* UserAgreementViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3F7CA626D65D38002912DE /* UserAgreementViewController.swift */; }; + 4F3F7CB226D65D38002912DE /* LayoutDefine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3F7CA726D65D38002912DE /* LayoutDefine.swift */; }; + 4F3F7CB526D65DE3002912DE /* GenerateTestUserSig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3F7CB426D65DE3002912DE /* GenerateTestUserSig.swift */; }; + 4F84BEB127798B4500BE13C5 /* CallingSelectUserTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F84BEAC27798B4500BE13C5 /* CallingSelectUserTableViewCell.swift */; }; + 4F84BEB227798B4500BE13C5 /* TRTCGroupCallingContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F84BEAD27798B4500BE13C5 /* TRTCGroupCallingContactViewController.swift */; }; + 4F84BEB327798B4500BE13C5 /* TRTCCallingContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F84BEAE27798B4500BE13C5 /* TRTCCallingContactViewController.swift */; }; + 4F84BEB427798B4500BE13C5 /* TRTCCallingContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F84BEAF27798B4500BE13C5 /* TRTCCallingContactView.swift */; }; + 4F84BEB527798B4500BE13C5 /* TRTCCallingCountryCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F84BEB027798B4500BE13C5 /* TRTCCallingCountryCodeView.swift */; }; + 4F84BECD2779A28100BE13C5 /* MainCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F84BEBA2779A28100BE13C5 /* MainCollectionViewCell.swift */; }; + 4F84BED02779A28100BE13C5 /* MainMenuCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F84BEBE2779A28100BE13C5 /* MainMenuCell.m */; }; + 4F84BED12779A28100BE13C5 /* MainMenuItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F84BEC12779A28100BE13C5 /* MainMenuItemModel.swift */; }; + 4F84BED22779A28100BE13C5 /* MainLocalized.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4F84BEC42779A28100BE13C5 /* MainLocalized.strings */; }; + 4F84BED42779A28100BE13C5 /* MainLocalized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F84BEC92779A28100BE13C5 /* MainLocalized.swift */; }; + 4F84BED52779A28100BE13C5 /* MainAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F84BECB2779A28100BE13C5 /* MainAssets.xcassets */; }; + 4F84BED72779AB6800BE13C5 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F84BED62779AB6800BE13C5 /* MainViewController.swift */; }; + 4FE7A785275E3A89009CCB09 /* phone_ringing.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 4FE7A784275E3A89009CCB09 /* phone_ringing.mp3 */; }; + 72398BB45AF374832D94AB71 /* Pods_TUICallingApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 382519A4D3CD8356970B4F9A /* Pods_TUICallingApp.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 2900AD2DB85102F00277DCDB /* Pods-TRTCCallingApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TRTCCallingApp.release.xcconfig"; path = "Target Support Files/Pods-TRTCCallingApp/Pods-TRTCCallingApp.release.xcconfig"; sourceTree = ""; }; + 2A653A99F0A29BA5B6E8ABF5 /* Pods-TUICallingApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TUICallingApp.release.xcconfig"; path = "Target Support Files/Pods-TUICallingApp/Pods-TUICallingApp.release.xcconfig"; sourceTree = ""; }; + 382519A4D3CD8356970B4F9A /* Pods_TUICallingApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TUICallingApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3C516BAB34F176BB5A4EE6D8 /* Pods-TUICallingApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TUICallingApp.debug.xcconfig"; path = "Target Support Files/Pods-TUICallingApp/Pods-TUICallingApp.debug.xcconfig"; sourceTree = ""; }; + 470EB6F1266A49170072071F /* LaunchImage@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "LaunchImage@2x.png"; sourceTree = ""; }; + 470EB6F2266A49170072071F /* LaunchImage_en@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "LaunchImage_en@2x.png"; sourceTree = ""; }; + 470EB6F3266A49170072071F /* LaunchImage@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "LaunchImage@3x.png"; sourceTree = ""; }; + 470EB6F4266A49170072071F /* LaunchImage_en@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "LaunchImage_en@3x.png"; sourceTree = ""; }; + 470EB6FA266A49200072071F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen_en.storyboard; sourceTree = ""; }; + 470EB6FF266A49380072071F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 470EB701266A493E0072071F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; + 4739C7D62657507800CA7C45 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/CallingLocalized.strings"; sourceTree = ""; }; + 4739C7D72657507800CA7C45 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/CallingLocalized.strings; sourceTree = ""; }; + 4739C7D82657507800CA7C45 /* CallingLocalized.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallingLocalized.swift; sourceTree = ""; }; + 4739C7DA2657507900CA7C45 /* CallingContact.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = CallingContact.xcassets; sourceTree = ""; }; + 474E0FE52644DFAE002AFADB /* TUICallingApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TUICallingApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 47D9D35E26521A7A0007EE1F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.strings"; sourceTree = ""; }; + 47D9D36026521A7A0007EE1F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Main.strings"; sourceTree = ""; }; + 47D9D36126521A7A0007EE1F /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 47D9D36226521A7A0007EE1F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 47D9D36426521A7A0007EE1F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 47D9D36626521A7A0007EE1F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 47D9D36726521A7A0007EE1F /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 47D9D36826521A7A0007EE1F /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 47D9D36926521A7A0007EE1F /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 4F09F32C27587C1400B5F5FE /* TUICallingApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TUICallingApp.entitlements; sourceTree = ""; }; + 4F3F7C9A26D65D38002912DE /* TRTCRegisterRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TRTCRegisterRootView.swift; sourceTree = ""; }; + 4F3F7C9B26D65D38002912DE /* TRTCLoginRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TRTCLoginRootView.swift; sourceTree = ""; }; + 4F3F7C9C26D65D38002912DE /* UserAgreementViewController+UI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserAgreementViewController+UI.swift"; sourceTree = ""; }; + 4F3F7C9F26D65D38002912DE /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LoginLocalized.strings"; sourceTree = ""; }; + 4F3F7CA026D65D38002912DE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/LoginLocalized.strings; sourceTree = ""; }; + 4F3F7CA126D65D38002912DE /* LoginLocalized.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginLocalized.swift; sourceTree = ""; }; + 4F3F7CA226D65D38002912DE /* TRTCLoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TRTCLoginViewController.swift; sourceTree = ""; }; + 4F3F7CA326D65D38002912DE /* ProfileManager_Mock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileManager_Mock.swift; sourceTree = ""; }; + 4F3F7CA426D65D38002912DE /* AppUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUtils.swift; sourceTree = ""; }; + 4F3F7CA526D65D38002912DE /* TRTCRegisterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TRTCRegisterViewController.swift; sourceTree = ""; }; + 4F3F7CA626D65D38002912DE /* UserAgreementViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAgreementViewController.swift; sourceTree = ""; }; + 4F3F7CA726D65D38002912DE /* LayoutDefine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutDefine.swift; sourceTree = ""; }; + 4F3F7CB426D65DE3002912DE /* GenerateTestUserSig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateTestUserSig.swift; sourceTree = ""; }; + 4F84BEAC27798B4500BE13C5 /* CallingSelectUserTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallingSelectUserTableViewCell.swift; sourceTree = ""; }; + 4F84BEAD27798B4500BE13C5 /* TRTCGroupCallingContactViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TRTCGroupCallingContactViewController.swift; sourceTree = ""; }; + 4F84BEAE27798B4500BE13C5 /* TRTCCallingContactViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TRTCCallingContactViewController.swift; sourceTree = ""; }; + 4F84BEAF27798B4500BE13C5 /* TRTCCallingContactView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TRTCCallingContactView.swift; sourceTree = ""; }; + 4F84BEB027798B4500BE13C5 /* TRTCCallingCountryCodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TRTCCallingCountryCodeView.swift; sourceTree = ""; }; + 4F84BEB92779A28100BE13C5 /* MainMenuCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainMenuCell.h; sourceTree = ""; }; + 4F84BEBA2779A28100BE13C5 /* MainCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainCollectionViewCell.swift; sourceTree = ""; }; + 4F84BEBE2779A28100BE13C5 /* MainMenuCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainMenuCell.m; sourceTree = ""; }; + 4F84BEBF2779A28100BE13C5 /* ColorMacro.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ColorMacro.h; sourceTree = ""; }; + 4F84BEC12779A28100BE13C5 /* MainMenuItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainMenuItemModel.swift; sourceTree = ""; }; + 4F84BEC52779A28100BE13C5 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainLocalized.strings"; sourceTree = ""; }; + 4F84BEC62779A28100BE13C5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainLocalized.strings; sourceTree = ""; }; + 4F84BEC92779A28100BE13C5 /* MainLocalized.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainLocalized.swift; sourceTree = ""; }; + 4F84BECB2779A28100BE13C5 /* MainAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MainAssets.xcassets; sourceTree = ""; }; + 4F84BED62779AB6800BE13C5 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; + 4FE7A784275E3A89009CCB09 /* phone_ringing.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = phone_ringing.mp3; sourceTree = ""; }; + 8F5D6C3B9ADE64ED3C2FC330 /* Pods-TRTCCallingApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TRTCCallingApp.debug.xcconfig"; path = "Target Support Files/Pods-TRTCCallingApp/Pods-TRTCCallingApp.debug.xcconfig"; sourceTree = ""; }; + 9932A0F6A2E0F892B8A8DE24 /* Pods-TRTCCalling.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TRTCCalling.debug.xcconfig"; path = "Target Support Files/Pods-TRTCCalling/Pods-TRTCCalling.debug.xcconfig"; sourceTree = ""; }; + C5B5B8F206D4592A066F3950 /* Pods-TRTCCalling.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TRTCCalling.release.xcconfig"; path = "Target Support Files/Pods-TRTCCalling/Pods-TRTCCalling.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 474E0FE22644DFAE002AFADB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 72398BB45AF374832D94AB71 /* Pods_TUICallingApp.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 45BC76B78942804520FA6869 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 382519A4D3CD8356970B4F9A /* Pods_TUICallingApp.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 4739C7D32657507800CA7C45 /* resource */ = { + isa = PBXGroup; + children = ( + 4FE7A784275E3A89009CCB09 /* phone_ringing.mp3 */, + 4739C7D42657507800CA7C45 /* localized */, + 4739C7D92657507900CA7C45 /* assets */, + ); + path = resource; + sourceTree = ""; + }; + 4739C7D42657507800CA7C45 /* localized */ = { + isa = PBXGroup; + children = ( + 4739C7D52657507800CA7C45 /* CallingLocalized.strings */, + 4739C7D82657507800CA7C45 /* CallingLocalized.swift */, + ); + path = localized; + sourceTree = ""; + }; + 4739C7D92657507900CA7C45 /* assets */ = { + isa = PBXGroup; + children = ( + 4739C7DA2657507900CA7C45 /* CallingContact.xcassets */, + ); + path = assets; + sourceTree = ""; + }; + 474E0FDC2644DFAE002AFADB = { + isa = PBXGroup; + children = ( + 4F09F32C27587C1400B5F5FE /* TUICallingApp.entitlements */, + 4F3F7CB326D65DE3002912DE /* Debug */, + 45BC76B78942804520FA6869 /* Frameworks */, + 914460EA7D352CBA688E2356 /* Pods */, + 474E0FE62644DFAE002AFADB /* Products */, + 47D9D35C26521A7A0007EE1F /* App */, + ); + sourceTree = ""; + }; + 474E0FE62644DFAE002AFADB /* Products */ = { + isa = PBXGroup; + children = ( + 474E0FE52644DFAE002AFADB /* TUICallingApp.app */, + ); + name = Products; + sourceTree = ""; + }; + 47C495AA2653BF3400B95452 /* Calling */ = { + isa = PBXGroup; + children = ( + 4739C7D32657507800CA7C45 /* resource */, + 47C495AB2653BF4000B95452 /* ui */, + ); + path = Calling; + sourceTree = ""; + }; + 47C495AB2653BF4000B95452 /* ui */ = { + isa = PBXGroup; + children = ( + 4F84BEAC27798B4500BE13C5 /* CallingSelectUserTableViewCell.swift */, + 4F84BEAF27798B4500BE13C5 /* TRTCCallingContactView.swift */, + 4F84BEAE27798B4500BE13C5 /* TRTCCallingContactViewController.swift */, + 4F84BEB027798B4500BE13C5 /* TRTCCallingCountryCodeView.swift */, + 4F84BEAD27798B4500BE13C5 /* TRTCGroupCallingContactViewController.swift */, + ); + path = ui; + sourceTree = ""; + }; + 47D9D35C26521A7A0007EE1F /* App */ = { + isa = PBXGroup; + children = ( + 470EB6F9266A49200072071F /* LaunchScreen_en.storyboard */, + 470EB6F2266A49170072071F /* LaunchImage_en@2x.png */, + 470EB6F4266A49170072071F /* LaunchImage_en@3x.png */, + 470EB6F1266A49170072071F /* LaunchImage@2x.png */, + 470EB6F3266A49170072071F /* LaunchImage@3x.png */, + 4F3F7C9926D65D38002912DE /* LoginMock */, + 4F84BEB62779A28100BE13C5 /* Main */, + 47C495AA2653BF3400B95452 /* Calling */, + 47D9D35D26521A7A0007EE1F /* LaunchScreen.strings */, + 47D9D35F26521A7A0007EE1F /* Main.strings */, + 47D9D36126521A7A0007EE1F /* ViewController.swift */, + 47D9D36226521A7A0007EE1F /* Assets.xcassets */, + 47D9D36326521A7A0007EE1F /* LaunchScreen.storyboard */, + 47D9D36526521A7A0007EE1F /* Main.storyboard */, + 47D9D36726521A7A0007EE1F /* AppDelegate.swift */, + 47D9D36826521A7A0007EE1F /* Info.plist */, + 47D9D36926521A7A0007EE1F /* SceneDelegate.swift */, + 470EB700266A49380072071F /* InfoPlist.strings */, + ); + path = App; + sourceTree = ""; + }; + 4F3F7C9926D65D38002912DE /* LoginMock */ = { + isa = PBXGroup; + children = ( + 4F3F7C9A26D65D38002912DE /* TRTCRegisterRootView.swift */, + 4F3F7C9B26D65D38002912DE /* TRTCLoginRootView.swift */, + 4F3F7C9C26D65D38002912DE /* UserAgreementViewController+UI.swift */, + 4F3F7C9D26D65D38002912DE /* AppLocalized */, + 4F3F7CA226D65D38002912DE /* TRTCLoginViewController.swift */, + 4F3F7CA326D65D38002912DE /* ProfileManager_Mock.swift */, + 4F3F7CA426D65D38002912DE /* AppUtils.swift */, + 4F3F7CA526D65D38002912DE /* TRTCRegisterViewController.swift */, + 4F3F7CA626D65D38002912DE /* UserAgreementViewController.swift */, + 4F3F7CA726D65D38002912DE /* LayoutDefine.swift */, + ); + path = LoginMock; + sourceTree = SOURCE_ROOT; + }; + 4F3F7C9D26D65D38002912DE /* AppLocalized */ = { + isa = PBXGroup; + children = ( + 4F3F7C9E26D65D38002912DE /* LoginLocalized.strings */, + 4F3F7CA126D65D38002912DE /* LoginLocalized.swift */, + ); + path = AppLocalized; + sourceTree = ""; + }; + 4F3F7CB326D65DE3002912DE /* Debug */ = { + isa = PBXGroup; + children = ( + 4F3F7CB426D65DE3002912DE /* GenerateTestUserSig.swift */, + ); + path = Debug; + sourceTree = ""; + }; + 4F84BEB62779A28100BE13C5 /* Main */ = { + isa = PBXGroup; + children = ( + 4F84BEB72779A28100BE13C5 /* ui */, + 4F84BEC02779A28100BE13C5 /* model */, + 4F84BEC22779A28100BE13C5 /* resource */, + ); + path = Main; + sourceTree = ""; + }; + 4F84BEB72779A28100BE13C5 /* ui */ = { + isa = PBXGroup; + children = ( + 4F84BED62779AB6800BE13C5 /* MainViewController.swift */, + 4F84BEBA2779A28100BE13C5 /* MainCollectionViewCell.swift */, + 4F84BEBF2779A28100BE13C5 /* ColorMacro.h */, + 4F84BEB92779A28100BE13C5 /* MainMenuCell.h */, + 4F84BEBE2779A28100BE13C5 /* MainMenuCell.m */, + ); + path = ui; + sourceTree = ""; + }; + 4F84BEC02779A28100BE13C5 /* model */ = { + isa = PBXGroup; + children = ( + 4F84BEC12779A28100BE13C5 /* MainMenuItemModel.swift */, + ); + path = model; + sourceTree = ""; + }; + 4F84BEC22779A28100BE13C5 /* resource */ = { + isa = PBXGroup; + children = ( + 4F84BEC32779A28100BE13C5 /* localized */, + 4F84BECA2779A28100BE13C5 /* assets */, + ); + path = resource; + sourceTree = ""; + }; + 4F84BEC32779A28100BE13C5 /* localized */ = { + isa = PBXGroup; + children = ( + 4F84BEC42779A28100BE13C5 /* MainLocalized.strings */, + 4F84BEC92779A28100BE13C5 /* MainLocalized.swift */, + ); + path = localized; + sourceTree = ""; + }; + 4F84BECA2779A28100BE13C5 /* assets */ = { + isa = PBXGroup; + children = ( + 4F84BECB2779A28100BE13C5 /* MainAssets.xcassets */, + ); + path = assets; + sourceTree = ""; + }; + 914460EA7D352CBA688E2356 /* Pods */ = { + isa = PBXGroup; + children = ( + 9932A0F6A2E0F892B8A8DE24 /* Pods-TRTCCalling.debug.xcconfig */, + C5B5B8F206D4592A066F3950 /* Pods-TRTCCalling.release.xcconfig */, + 8F5D6C3B9ADE64ED3C2FC330 /* Pods-TRTCCallingApp.debug.xcconfig */, + 2900AD2DB85102F00277DCDB /* Pods-TRTCCallingApp.release.xcconfig */, + 3C516BAB34F176BB5A4EE6D8 /* Pods-TUICallingApp.debug.xcconfig */, + 2A653A99F0A29BA5B6E8ABF5 /* Pods-TUICallingApp.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 474E0FE42644DFAE002AFADB /* TUICallingApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 474E0FF92644DFAF002AFADB /* Build configuration list for PBXNativeTarget "TUICallingApp" */; + buildPhases = ( + 13AD8BEF83F6C34C3FD50D30 /* [CP] Check Pods Manifest.lock */, + 474E0FE12644DFAE002AFADB /* Sources */, + 474E0FE22644DFAE002AFADB /* Frameworks */, + 474E0FE32644DFAE002AFADB /* Resources */, + 8FE026B3F7BE01BC7F9DB305 /* [CP] Embed Pods Frameworks */, + 7B8A2F1A0F8FE0F179693A5F /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TUICallingApp; + productName = TRTCCalling; + productReference = 474E0FE52644DFAE002AFADB /* TUICallingApp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 474E0FDD2644DFAE002AFADB /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1240; + LastUpgradeCheck = 1240; + TargetAttributes = { + 474E0FE42644DFAE002AFADB = { + CreatedOnToolsVersion = 12.4; + LastSwiftMigration = 1250; + }; + }; + }; + buildConfigurationList = 474E0FE02644DFAE002AFADB /* Build configuration list for PBXProject "TUICallingApp" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + "zh-Hans", + English, + ); + mainGroup = 474E0FDC2644DFAE002AFADB; + productRefGroup = 474E0FE62644DFAE002AFADB /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 474E0FE42644DFAE002AFADB /* TUICallingApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 474E0FE32644DFAE002AFADB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 47D9D36A26521A7A0007EE1F /* LaunchScreen.strings in Resources */, + 470EB6FE266A49380072071F /* InfoPlist.strings in Resources */, + 470EB6F5266A49170072071F /* LaunchImage@2x.png in Resources */, + 4F84BED22779A28100BE13C5 /* MainLocalized.strings in Resources */, + 470EB6F7266A49170072071F /* LaunchImage@3x.png in Resources */, + 4F3F7CAB26D65D38002912DE /* LoginLocalized.strings in Resources */, + 4F84BED52779A28100BE13C5 /* MainAssets.xcassets in Resources */, + 47D9D36F26521A7A0007EE1F /* Main.storyboard in Resources */, + 47D9D36E26521A7A0007EE1F /* LaunchScreen.storyboard in Resources */, + 470EB6FB266A49200072071F /* LaunchScreen_en.storyboard in Resources */, + 4739C7DB2657507900CA7C45 /* CallingLocalized.strings in Resources */, + 470EB6F6266A49170072071F /* LaunchImage_en@2x.png in Resources */, + 4739C7DD2657507900CA7C45 /* CallingContact.xcassets in Resources */, + 4739C7DF265750A800CA7C45 /* Assets.xcassets in Resources */, + 47D9D36B26521A7A0007EE1F /* Main.strings in Resources */, + 4FE7A785275E3A89009CCB09 /* phone_ringing.mp3 in Resources */, + 470EB6F8266A49170072071F /* LaunchImage_en@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 13AD8BEF83F6C34C3FD50D30 /* [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-TUICallingApp-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; + }; + 7B8A2F1A0F8FE0F179693A5F /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-TUICallingApp/Pods-TUICallingApp-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-TUICallingApp/Pods-TUICallingApp-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TUICallingApp/Pods-TUICallingApp-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 8FE026B3F7BE01BC7F9DB305 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-TUICallingApp/Pods-TUICallingApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-TUICallingApp/Pods-TUICallingApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TUICallingApp/Pods-TUICallingApp-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 474E0FE12644DFAE002AFADB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4F84BEB527798B4500BE13C5 /* TRTCCallingCountryCodeView.swift in Sources */, + 4F84BED12779A28100BE13C5 /* MainMenuItemModel.swift in Sources */, + 4F84BED42779A28100BE13C5 /* MainLocalized.swift in Sources */, + 47D9D37026521A7A0007EE1F /* AppDelegate.swift in Sources */, + 4F84BEB427798B4500BE13C5 /* TRTCCallingContactView.swift in Sources */, + 4F3F7CAC26D65D38002912DE /* LoginLocalized.swift in Sources */, + 4739C7DC2657507900CA7C45 /* CallingLocalized.swift in Sources */, + 4F84BEB327798B4500BE13C5 /* TRTCCallingContactViewController.swift in Sources */, + 4F84BECD2779A28100BE13C5 /* MainCollectionViewCell.swift in Sources */, + 47D9D36C26521A7A0007EE1F /* ViewController.swift in Sources */, + 4F3F7CAF26D65D38002912DE /* AppUtils.swift in Sources */, + 4F3F7CB526D65DE3002912DE /* GenerateTestUserSig.swift in Sources */, + 4F3F7CB126D65D38002912DE /* UserAgreementViewController.swift in Sources */, + 4F3F7CAD26D65D38002912DE /* TRTCLoginViewController.swift in Sources */, + 4F84BED72779AB6800BE13C5 /* MainViewController.swift in Sources */, + 4F84BED02779A28100BE13C5 /* MainMenuCell.m in Sources */, + 4F3F7CB026D65D38002912DE /* TRTCRegisterViewController.swift in Sources */, + 4F3F7CB226D65D38002912DE /* LayoutDefine.swift in Sources */, + 4F3F7CA826D65D38002912DE /* TRTCRegisterRootView.swift in Sources */, + 4F84BEB127798B4500BE13C5 /* CallingSelectUserTableViewCell.swift in Sources */, + 4F84BEB227798B4500BE13C5 /* TRTCGroupCallingContactViewController.swift in Sources */, + 4F3F7CA926D65D38002912DE /* TRTCLoginRootView.swift in Sources */, + 4F3F7CAA26D65D38002912DE /* UserAgreementViewController+UI.swift in Sources */, + 47D9D37226521A7A0007EE1F /* SceneDelegate.swift in Sources */, + 4F3F7CAE26D65D38002912DE /* ProfileManager_Mock.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 470EB6F9266A49200072071F /* LaunchScreen_en.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 470EB6FA266A49200072071F /* Base */, + ); + name = LaunchScreen_en.storyboard; + sourceTree = ""; + }; + 470EB700266A49380072071F /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 470EB6FF266A49380072071F /* en */, + 470EB701266A493E0072071F /* zh-Hans */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 4739C7D52657507800CA7C45 /* CallingLocalized.strings */ = { + isa = PBXVariantGroup; + children = ( + 4739C7D62657507800CA7C45 /* zh-Hans */, + 4739C7D72657507800CA7C45 /* en */, + ); + name = CallingLocalized.strings; + sourceTree = ""; + }; + 47D9D35D26521A7A0007EE1F /* LaunchScreen.strings */ = { + isa = PBXVariantGroup; + children = ( + 47D9D35E26521A7A0007EE1F /* zh-Hans */, + ); + name = LaunchScreen.strings; + sourceTree = ""; + }; + 47D9D35F26521A7A0007EE1F /* Main.strings */ = { + isa = PBXVariantGroup; + children = ( + 47D9D36026521A7A0007EE1F /* zh-Hans */, + ); + name = Main.strings; + sourceTree = ""; + }; + 47D9D36326521A7A0007EE1F /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 47D9D36426521A7A0007EE1F /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + 47D9D36526521A7A0007EE1F /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 47D9D36626521A7A0007EE1F /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 4F3F7C9E26D65D38002912DE /* LoginLocalized.strings */ = { + isa = PBXVariantGroup; + children = ( + 4F3F7C9F26D65D38002912DE /* zh-Hans */, + 4F3F7CA026D65D38002912DE /* en */, + ); + name = LoginLocalized.strings; + sourceTree = ""; + }; + 4F84BEC42779A28100BE13C5 /* MainLocalized.strings */ = { + isa = PBXVariantGroup; + children = ( + 4F84BEC52779A28100BE13C5 /* zh-Hans */, + 4F84BEC62779A28100BE13C5 /* en */, + ); + name = MainLocalized.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 474E0FF72644DFAF002AFADB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_BITCODE = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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 = 10.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 474E0FF82644DFAF002AFADB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_BITCODE = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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 = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 474E0FFA2644DFAF002AFADB /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3C516BAB34F176BB5A4EE6D8 /* Pods-TUICallingApp.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = TUICallingApp.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = F8A3GH6Q4W; + ENABLE_BITCODE = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/TRTCCalling/TUICalling.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/TXAppBasic/TXAppBasic.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Toast-Swift/Toast_Swift.framework/Headers\"", + "${PODS_ROOT}/TXImSDK_Plus_iOS/ImSDK_Plus.framework/Headers/", + ); + INFOPLIST_FILE = App/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-l\"c++\"", + "-l\"resolv\"", + "-l\"sqlite3\"", + "-framework", + "\"AVFoundation\"", + "-framework", + "\"Accelerate\"", + "-framework", + "\"Alamofire\"", + "-framework", + "\"AssetsLibrary\"", + "-framework", + "\"CFNetwork\"", + "-framework", + "\"ImSDK_Plus\"", + "-framework", + "\"Kingfisher\"", + "-framework", + "\"QuartzCore\"", + "-framework", + "\"SnapKit\"", + "-framework", + "\"TUICalling\"", + "-framework", + "\"TXAppBasic\"", + "-framework", + "\"Toast_Swift\"", + "-weak_framework", + "\"Combine\"", + "-weak_framework", + "\"SwiftUI\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.tencent.TRTCScene; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = abyWildcardDev; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 474E0FFB2644DFAF002AFADB /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2A653A99F0A29BA5B6E8ABF5 /* Pods-TUICallingApp.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = TUICallingApp.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = F8A3GH6Q4W; + ENABLE_BITCODE = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/TRTCCalling/TUICalling.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/TXAppBasic/TXAppBasic.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Toast-Swift/Toast_Swift.framework/Headers\"", + "${PODS_ROOT}/TXImSDK_Plus_iOS/ImSDK_Plus.framework/Headers/", + ); + INFOPLIST_FILE = App/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-l\"c++\"", + "-l\"resolv\"", + "-l\"sqlite3\"", + "-framework", + "\"AVFoundation\"", + "-framework", + "\"Accelerate\"", + "-framework", + "\"Alamofire\"", + "-framework", + "\"AssetsLibrary\"", + "-framework", + "\"CFNetwork\"", + "-framework", + "\"ImSDK_Plus\"", + "-framework", + "\"Kingfisher\"", + "-framework", + "\"QuartzCore\"", + "-framework", + "\"SnapKit\"", + "-framework", + "\"TUICalling\"", + "-framework", + "\"TXAppBasic\"", + "-framework", + "\"Toast_Swift\"", + "-weak_framework", + "\"Combine\"", + "-weak_framework", + "\"SwiftUI\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.tencent.TRTCScene; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = abyWildcardDev; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 474E0FE02644DFAE002AFADB /* Build configuration list for PBXProject "TUICallingApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 474E0FF72644DFAF002AFADB /* Debug */, + 474E0FF82644DFAF002AFADB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 474E0FF92644DFAF002AFADB /* Build configuration list for PBXNativeTarget "TUICallingApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 474E0FFA2644DFAF002AFADB /* Debug */, + 474E0FFB2644DFAF002AFADB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 474E0FDD2644DFAE002AFADB /* Project object */; +} diff --git a/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcodeproj/xcshareddata/xcschemes/TUICallingApp.xcscheme b/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcodeproj/xcshareddata/xcschemes/TUICallingApp.xcscheme new file mode 100644 index 000000000..16151b4fd --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcodeproj/xcshareddata/xcschemes/TUICallingApp.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcworkspace/contents.xcworkspacedata b/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..e060295de --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcworkspace/xcuserdata/gg.xcuserdatad/UserInterfaceState.xcuserstate b/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcworkspace/xcuserdata/gg.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 000000000..334640912 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcworkspace/xcuserdata/gg.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcworkspace/xcuserdata/gg.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcworkspace/xcuserdata/gg.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 000000000..074c892d7 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/TUICallingApp.xcworkspace/xcuserdata/gg.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Extension/String+Extensions/String+byteLength.swift b/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Extension/String+Extensions/String+byteLength.swift new file mode 100644 index 000000000..39c4f2997 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Extension/String+Extensions/String+byteLength.swift @@ -0,0 +1,37 @@ +// +// String+byteLength.swift +// Alamofire +// +// Created by gg on 2021/8/3. +// + +import Foundation + +public let createRoomTextMaxByteLength = 30 + +extension String { + public func byteLength() -> Int { + guard let data = data(using: .utf8) else { + return 0 + } + return data.count + } + public func subString(toByteLength: Int) -> String { + guard let data = data(using: .utf8) else { + return "" + } + if data.count > toByteLength { + var offset = 0 + while offset < toByteLength { + if let str = String(data: data[0..<(toByteLength - offset)], encoding: .utf8) { + return str + } + offset += 1 + } + return "" + } + else { + return self + } + } +} diff --git a/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Extension/UIColor+Extensions/UIColor+hex.swift b/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Extension/UIColor+Extensions/UIColor+hex.swift new file mode 100644 index 000000000..b81f0ab6c --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Extension/UIColor+Extensions/UIColor+hex.swift @@ -0,0 +1,81 @@ +// +// UIColor+hex.swift +// TRTCVoiceRoomDemo +// +// Created by abyyxwang on 2020/6/4. +// Copyright © 2020 tencent. All rights reserved. +// + +import UIKit + + +extension UIColor { + public convenience init(red: Int, green: Int, blue: Int, alpha: CGFloat = 1.0) { + self.init( + red: CGFloat(red) / 255.0, + green: CGFloat(green) / 255.0, + blue: CGFloat(blue) / 255.0, + alpha: alpha + ) + } + + // 设置颜色值 + // 通过 十六进制与alpha来设置颜色值 ( 样式:0xff00ff ) + public convenience init(_ hex: Int, alpha: CGFloat = 1.0) { + assert(0...0xFFFFFF ~= hex, "The color hex value must between 0 to 0XFFFFFF") + let red = (hex & 0xFF0000) >> 16 + let green = (hex & 0x00FF00) >> 8 + let blue = (hex & 0x0000FF) + self.init(red: red, green: green, blue: blue, alpha: alpha) + } + + public convenience init(_ hex: String) { + var red: CGFloat = 0.0 + var green: CGFloat = 0.0 + var blue: CGFloat = 0.0 + var alpha: CGFloat = 1.0 + let scanner = Scanner.init(string: hex) + var hexValue: CUnsignedLongLong = 0 + if scanner.scanHexInt64(&hexValue) { + switch (hex.count) { + case 3: + red = CGFloat((hexValue & 0xF00) >> 8) / 15.0 + green = CGFloat((hexValue & 0x0F0) >> 4) / 15.0 + blue = CGFloat(hexValue & 0x00F) / 15.0 + case 4: + red = CGFloat((hexValue & 0xF000) >> 12) / 15.0 + green = CGFloat((hexValue & 0x0F00) >> 8) / 15.0 + blue = CGFloat((hexValue & 0x00F0) >> 4) / 15.0 + alpha = CGFloat(hexValue & 0x000F) / 15.0 + case 6: + red = CGFloat((hexValue & 0xFF0000) >> 16) / 255.0 + green = CGFloat((hexValue & 0x00FF00) >> 8) / 255.0 + blue = CGFloat(hexValue & 0x0000FF) / 255.0 + case 8: + red = CGFloat((hexValue & 0xFF000000) >> 24) / 255.0 + green = CGFloat((hexValue & 0x00FF0000) >> 16) / 255.0 + blue = CGFloat((hexValue & 0x0000FF00) >> 8) / 255.0 + alpha = CGFloat(hexValue & 0x000000FF) / 255.0 + default: + break + } + } + self.init(red: red, green: green, blue: blue, alpha: alpha) + } + + // 颜色转化为图片 + public func trans2Image() -> UIImage { + let rect = CGRect(x: 0, y: 0, width: 1.0, height: 1.0) + UIGraphicsBeginImageContext(rect.size) + let context = UIGraphicsGetCurrentContext() + context?.setFillColor(self.cgColor) + context?.fill(rect) + let theImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return theImage ?? UIImage() + } +} + +public extension UIColor { + public static let themeTintColor = UIColor.init(0x5E6FA3) +} diff --git a/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Extension/UIView+Extensions/UIView+roundrect.swift b/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Extension/UIView+Extensions/UIView+roundrect.swift new file mode 100644 index 000000000..4ba62a764 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Extension/UIView+Extensions/UIView+roundrect.swift @@ -0,0 +1,93 @@ +// +// UIView+roundrect.swift +// TRTCScene +// +// Created by adams on 2021/4/8. +// + +import Foundation + +public extension UIView { + /// 切部分圆角 + /// + /// - Parameters: + /// - rect: 传入View的Rect + /// - byRoundingCorners: 裁剪位置 + /// - cornerRadii: 裁剪半径 + func roundedRect(rect:CGRect, byRoundingCorners: UIRectCorner, cornerRadii: CGSize) { + let maskPath = UIBezierPath.init(roundedRect: rect, byRoundingCorners: byRoundingCorners, cornerRadii: cornerRadii) + let maskLayer = CAShapeLayer.init() + maskLayer.frame = bounds + maskLayer.path = maskPath.cgPath + self.layer.mask = maskLayer + } + + /// 切圆角 + /// + /// - Parameter rect: 传入view的Rect + func roundedCircle(rect: CGRect) { + roundedRect(rect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: bounds.size.width / 2, height: bounds.size.height / 2)) + } +} + +public extension UIView { + + private struct AssociatedKeys { + static var gradientLayerKey = "gradientLayerKey" + } + + var gradientLayer: CAGradientLayer? { + get { + return objc_getAssociatedObject(self, &AssociatedKeys.gradientLayerKey) as? CAGradientLayer + } + set { + objc_setAssociatedObject(self, &AssociatedKeys.gradientLayerKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + + func removeGradientLayer() { + guard let glayer = gradientLayer else { + return + } + glayer.removeFromSuperlayer() + gradientLayer = nil + } + + @discardableResult + func gradient(colors: [CGColor]) -> CAGradientLayer { + + func createGradientLayer() -> CAGradientLayer { + let gradientLayer = CAGradientLayer() + gradientLayer.startPoint = .zero + gradientLayer.endPoint = CGPoint(x: 1, y: 0) + gradientLayer.locations = [0, 1] + self.gradientLayer = gradientLayer + return gradientLayer + } + + guard let sublayers = layer.sublayers else { + let gradientLayer = createGradientLayer() + gradientLayer.colors = colors + gradientLayer.frame = bounds + layer.insertSublayer(gradientLayer, at: 0) + self.gradientLayer = gradientLayer + return gradientLayer + } + for sublayer in sublayers { + if sublayer.isKind(of: CAGradientLayer.self) { + let glayer = sublayer as! CAGradientLayer + if let gcolors = glayer.colors as? [CGColor], gcolors == colors { + glayer.frame = bounds + self.gradientLayer = glayer + return glayer + } + } + } + let gradientLayer = createGradientLayer() + gradientLayer.colors = colors + gradientLayer.frame = bounds + layer.insertSublayer(gradientLayer, at: 0) + self.gradientLayer = gradientLayer + return gradientLayer + } +} diff --git a/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Utils/CommonUtils.swift b/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Utils/CommonUtils.swift new file mode 100644 index 000000000..cc4f77df5 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Utils/CommonUtils.swift @@ -0,0 +1,112 @@ +// +// CommonUtils.swift +// trtcScenesDemo +// +// Created by xcoderliu on 12/19/19. +// Copyright © 2019 xcoderliu. All rights reserved. +// + +import Foundation + +public extension UIStackView { + + func safelyRemoveArrangedSubviews() { + + // Remove all the arranged subviews and save them to an array + let removedSubviews = arrangedSubviews.reduce([]) { (sum, next) -> [UIView] in + self.removeArrangedSubview(next) + return sum + [next] + } + + // Deactive all constraints at once + NSLayoutConstraint.deactivate(removedSubviews.flatMap({ $0.constraints })) + + // Remove the views from self + removedSubviews.forEach({ $0.removeFromSuperview() }) + } +} + +public extension UIColor { + + @objc static var appTint: UIColor { + return UIColor(hex: "006EFF") ?? UIColor(red: 54.0 / 255.0, green: 134.0 / 255.0, blue: 246.0 / 255.0, alpha: 1.0) + } + + @objc static var searchBarBackColor: UIColor { + return UIColor(hex: "#0D2C5B") ?? UIColor(red: 13.0 / 255.0, green: 44.0 / 255.0, blue: 91.0 / 255.0, alpha: 1.0) + } + + @objc static var placeholderBackColor: UIColor { + return UIColor(hex: "#6B82A8") ?? UIColor(red: 107.0 / 255.0, green: 130.0 / 255.0, blue: 168.0 / 255.0, alpha: 1.0) + } + + @objc static var pannelBackColor: UIColor { + return UIColor(hex: "#13233F") ?? UIColor(red: 19.0 / 255.0, green: 35.0 / 255.0, blue: 63.0 / 255.0, alpha: 1.0) + } + + @objc static var buttonBackColor: UIColor { + return UIColor(hex: "#0062E3") ?? UIColor(red: 0.12, green: 0.23, blue: 0.4, alpha: 1.0) + } + + @objc static var verifyCodeBackColor: UIColor { + return UIColor(hex: "#006EFD") ?? UIColor(red: 0.0, green: 110.0 / 255.0, blue: 253.0 / 255.0, alpha: 1.0) + } + + @objc static var appBackGround: UIColor { + return UIColor(hex: "#242424") ?? .black + } + + @objc static var inputImageBackColor: UIColor { + return UIColor(hex: "#092650") ?? UIColor(red: 9.0 / 255.0, green: 38.0 / 255.0, blue: 80.0 / 255.0, alpha: 1.0) + } + + // MARK: - Initialization + @objc convenience init?(hex: String) { + var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines) + hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "") + var rgb: UInt64 = 0 + var r: CGFloat = 0.0 + var g: CGFloat = 0.0 + var b: CGFloat = 0.0 + var a: CGFloat = 1.0 + let length = hexSanitized.count + guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil } + if length == 6 { + r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0 + g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0 + b = CGFloat(rgb & 0x0000FF) / 255.0 + + } else if length == 8 { + r = CGFloat((rgb & 0xFF000000) >> 24) / 255.0 + g = CGFloat((rgb & 0x00FF0000) >> 16) / 255.0 + b = CGFloat((rgb & 0x0000FF00) >> 8) / 255.0 + a = CGFloat(rgb & 0x000000FF) / 255.0 + + } else { + return nil + } + self.init(red: r, green: g, blue: b, alpha: a) + } +} + +extension Dictionary { + func toJsonString() -> String { + if let json = try? JSONSerialization.data(withJSONObject: self, options: []), + let jsonStr = String(data: json, encoding: .utf8) + { + return jsonStr + } + fatalError() + } +} + +extension String { + func toJson() -> [String: Any]? { + if let infoData = data(using: .utf8), + let dict = try? JSONSerialization.jsonObject(with: infoData, options: []) as? [String: Any] + { + return dict + } + return nil + } +} diff --git a/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Utils/LayoutDefine.swift b/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Utils/LayoutDefine.swift new file mode 100644 index 000000000..4dc451a30 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Utils/LayoutDefine.swift @@ -0,0 +1,51 @@ +// +// LayoutDefine.swift +// TXLiteAVDemo +// +// Created by gg on 2021/3/15. +// Copyright © 2021 Tencent. All rights reserved. +// + +import Foundation + +public let ScreenWidth = UIScreen.main.bounds.width +public let ScreenHeight = UIScreen.main.bounds.height + +public let kDeviceIsIphoneX : Bool = { + if UIDevice.current.userInterfaceIdiom == .pad { + return false + } + let size = UIScreen.main.bounds.size + let notchValue = Int(size.width/size.height*100) + if notchValue == 216 || notchValue == 46 { + return true + } + return false +}() + +public let kDeviceSafeTopHeight : CGFloat = { + if kDeviceIsIphoneX { + return 44 + } + else { + return 20 + } +}() + +public let kDeviceSafeBottomHeight : CGFloat = { + if kDeviceIsIphoneX { + return 34 + } + else { + return 0 + } +}() + +public func convertPixel(w:CGFloat) -> CGFloat { + return w/375.0*ScreenWidth +} + +public func convertPixel(h:CGFloat) -> CGFloat { + return h/812.0*ScreenHeight +} + diff --git a/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Utils/TRTCLog.swift b/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Utils/TRTCLog.swift new file mode 100644 index 000000000..c75c3d4b6 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/TXAppBasic/AppBasicSource/Utils/TRTCLog.swift @@ -0,0 +1,148 @@ +// +// TRTCLog.swift +// TRTCVoiceRoomDemo +// +// Created by abyyxwang on 2020/6/10. +// Copyright © 2020 tencent. All rights reserved. +// + +import Foundation + +import Foundation +import os + +/// subsystem for current project. +/// - setup it when start a new project. +//private let subsystem = "com.slideart.photo2videomaker" +private let subSystemIdentifier: String = Bundle.main.bundleIdentifier ?? "com.tencent.trtc" +private let subsystem: String = "TRTC" +/// Custom log system. +public class TRTCLog { + public struct Output: OptionSet { + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public static let info = Output(rawValue: 1 << 0) + public static let debug = Output(rawValue: 1 << 1) + public static let warning = Output(rawValue: 1 << 2) + public static let error = Output(rawValue: 1 << 3) + public static let print = Output(rawValue: 1 << 4) + + public static let all: Output = [.print, .info, .debug, .warning, .error] + } + + /// setup log level and when does the log work. + public static var output: Output = [.debug, .warning, .error, .print] + + @available(iOS 10.0, *) + /// level info + static let infoLog = OSLog(subsystem: subsystem, category: "INFO") + /// info level + /// + /// - Parameters: + /// - string: log info description + /// - fileName: file name + /// - methodName: method name + /// - lineNumber: line number + public static func info(_ string: String, fileName: String = #file, methodName: String = #function, lineNumber: Int = #line) { + #if DEBUG + let file = (fileName as NSString).lastPathComponent.replacingOccurrences(of: ".Swift", with: "") + let log = "\(file):line \(lineNumber) method:\(methodName):\(string)" + if output.contains(.info) { + if #available(iOS 10.0, *) { + os_log("%@", log: infoLog, type: .info, log) + } else { + print(": %@", log) + } + } + #endif + } + + @available(iOS 10.0, *) + /// level debug + static let debugLog = OSLog(subsystem: subsystem, category: "DEBUG") + /// debug level + /// + /// - Parameters: + /// - string: log info description + /// - fileName: file name + /// - methodName: method name + /// - lineNumber: line number + public static func debug(_ string: String, fileName: String = #file, methodName: String = #function, lineNumber: Int = #line) { + #if DEBUG + let file = (fileName as NSString).lastPathComponent.replacingOccurrences(of: ".Swift", with: "") + let log = "\(file):line \(lineNumber) method:\(methodName):\(string)" + if output.contains(.debug) { + if #available(iOS 10.0, *) { + os_log("%@", log: debugLog, type: .debug, log) + } else { + print(": %@", log) + } + } + #endif + } + + @available(iOS 10.0, *) + /// level warning + static let warningLog = OSLog(subsystem: subsystem, category: "WARNING") + public static func warning(_ string: String, fileName: String = #file, methodName: String = #function, lineNumber: Int = #line) { + if output.contains(.warning) { + let file = (fileName as NSString).lastPathComponent.replacingOccurrences(of: ".Swift", with: "") + let log = "\(file):line \(lineNumber) method:\(methodName):\(string)" + if #available(iOS 10.0, *) { + os_log("%@", log: warningLog, type: .fault, log) + } else { + print(": %@", string) + } + } + } + + @available(iOS 10.0, *) + /// level error + static let errorLog = OSLog(subsystem: subsystem, category: "ERROR") + + /// log error + /// + /// - Parameters: + /// - string: message + /// - fileName: filename + /// - methodName: method + /// - lineNumber: lineNumber + public static func error(_ string: String, fileName: String = #file, methodName: String = #function, lineNumber: Int = #line) { + if output.contains(.error) { + let file = (fileName as NSString).lastPathComponent.replacingOccurrences(of: ".Swift", with: "") + let log = "\(file):line \(lineNumber) method:\(methodName):\n\(string)" + if #available(iOS 10.0, *) { + os_log("%@", log: errorLog, type: .error, log) + } else { + print(": %@", string) + } + #if DEBUG +// assert(false, log) + #endif + } + } + + /// just output in console. + /// + /// - Parameters: + /// - message: output message. + /// - fileName: file name. + /// - methodName: method name. + /// - lineNumber: line number. + public static func out(message: N, fileName: String = #file, methodName: String = #function, lineNumber: Int = #line) { + #if DEBUG + if output.contains(.print) { + let file = (fileName as NSString).lastPathComponent.replacingOccurrences(of: ".Swift", with: "") + print("[PRINT]\(file):\(lineNumber) \(methodName):\(message)") + } + #endif + } + + public static func out(_ message: N, fileName: String = #file, methodName: String = #function, lineNumber: Int = #line) { + TRTCLog.out(message: message, fileName: fileName, methodName: methodName, lineNumber: lineNumber) + } +} diff --git a/iOS/TUIKit/TUICalling/Example/TXAppBasic/TXAppBasic.podspec b/iOS/TUIKit/TUICalling/Example/TXAppBasic/TXAppBasic.podspec new file mode 100644 index 000000000..b10338eae --- /dev/null +++ b/iOS/TUIKit/TUICalling/Example/TXAppBasic/TXAppBasic.podspec @@ -0,0 +1,26 @@ +Pod::Spec.new do |spec| + + spec.name = "TXAppBasic" + spec.version = "1.0.0" + spec.platform = :ios + spec.ios.deployment_target = '11.0' + spec.license = { :type => 'Proprietary', + :text => <<-LICENSE + copyright 2017 tencent Ltd. All rights reserved. + LICENSE + } + spec.homepage = 'https://cloud.tencent.com/document/product/269/3794' + spec.documentation_url = 'https://cloud.tencent.com/document/product/269/9147' + spec.authors = 'tencent video cloud' + spec.summary = 'TXAppBasic' + spec.xcconfig = { 'VALID_ARCHS' => 'armv7 arm64 x86_64' } + spec.swift_version = '5.0' + + spec.source = { :path => './'} + spec.source_files = '**/AppBasicSource/**/*.{swift,h,m,mm}', '**/AppBasicSource/*.{swift,h,m,mm}' + spec.pod_target_xcconfig = { + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' + } + spec.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' } + +end diff --git a/iOS/TUIKit/TUICalling/README.md b/iOS/TUIKit/TUICalling/README.md index 883c17385..d956dc736 100644 --- a/iOS/TUIKit/TUICalling/README.md +++ b/iOS/TUIKit/TUICalling/README.md @@ -1,79 +1,108 @@ +本文档主要介绍如何快速集成实时音视频(TRTC)SDK,运行 TRTC 场景化解决方案,实现实时视频/语音通话。 + +## 目录结构 + +``` +TUICalling +├─ App // 视频/语音通话主页UI代码以及用到的图片及国际化字符串资源文件夹 +├─ Debug // 工程调试运行所需的关键业务代码文件夹 +├─ LoginMock // 登录UI及业务逻辑代码文件夹 +├─ Resources // 视频/语音通话功能所需的图片、国际化字符串资源文件夹 +├─ Source // 视频/语音通话核心业务逻辑代码文件夹 +└─ TXAppBasic // 工程依赖的基础组件 +``` + +## 环境准备 +- Xcode 11.0及以上版本 +- 最低支持系统:iOS 13.0 +- 请确保您的项目已设置有效的开发者签名 + +## 运行示例 + +### 前提条件 +- 您已 [注册腾讯云](https://cloud.tencent.com/document/product/378/17985) 账号,并完成 [实名认证](https://cloud.tencent.com/document/product/378/3629)。 + +### 申请 SDKAPPID 和 SECRETKEY +1. 登录实时音视频控制台,选择【开发辅助】>【[快速跑通Demo](https://console.cloud.tencent.com/trtc/quickstart)】。 +2. 单击【立即开始】,输入您的应用名称,例如`TestTRTC`,单击【创建应用】。 + +3. 创建应用完成后,单击【我已下载,下一步】,可以查看 SDKAppID 和密钥信息。 -# 实时语音/视频通话 +### 集成SDK +1. 工程默认集成的是`TXLiteAVSDK_TRTC`精简版SDK,您可通过【[官网链接](https://cloud.tencent.com/document/product/647/32689)】了解此版本SDK的具体功能。 +2. SDK集成方式默认使用`Cocoapods`,工程目录下`Podfile`文件内已帮您添加了SDK的依赖`pod 'TXLiteAVSDK_TRTC'`,您只需要打开终端进入到工程目录下执行`pod install`,SDK就会自动集成。 -## 概述 +### 配置工程文件 +1. 使用Xcode(11.0及以上)打开源码工程`TUICallingApp.xcworkspace`。 +2. 工程内找到`TUICalling/Debug/GenerateTestUserSig.swift`文件 。 +3. 设置`GenerateTestUserSig.swift`文件中的相关参数: +
    +
  • SDKAPPID:默认为 0 ,请设置为实际申请的SDKAPPID。
  • +
  • SECRETKEY:默认为空字符串,请设置为实际申请的SECRETKEY。
  • +
+ +4. 返回实时音视频控制台,单击【粘贴完成,下一步】。 +5. 单击【关闭指引,进入控制台管理应用】。 -**TUI组件化解决方案**是腾讯云TRTC针对直播、语聊、视频通话等推出的低代码解决方案,依托腾讯在音视频&通信领域的技术积累,帮助开发者快速实现相关业务场景,聚焦核心业务,助力业务起飞! +>!本文提到的生成 UserSig 的方案是在客户端代码中配置 SECRETKEY,该方法中 SECRETKEY 很容易被反编译逆向破解,一旦您的密钥泄露,攻击者就可以盗用您的腾讯云流量,因此**该方法仅适合本地跑通工程和功能调试**。 +>正确的 UserSig 签发方式是将 UserSig 的计算代码集成到您的服务端,并提供面向 App 的接口,在需要 UserSig 时由您的 App 向业务服务器发起请求获取动态 UserSig。更多详情请参见 [服务端生成 UserSig](https://cloud.tencent.com/document/product/647/17275#Server)。 -- [视频互动直播-TUILiveRoom](https://github.com/tencentyun/TUILiveRoom/) -- [多人视频会议-TUIMeeting](https://github.com/tencentyun/TUIMeeting/) -- [语音聊天室-TUIVoiceRoom](https://github.com/tencentyun/TUIVoiceRoom/) -- [语音沙龙-TUIChatSalon](https://github.com/tencentyun/TUIChatSalon/) -- [Karaoke-TUIKaraoke](https://github.com/tencentyun/TUIKaraoke/) -- [一起合唱-TUIChorus](https://github.com/tencentyun/TUIChorus/) +### 编译运行 +- 使用 Xcode(11.0及以上的版本)打开源码工程 `TUICalling/TUICallingApp.xcworkspace`,单击【运行】即可开始调试本 App。 -更多组件化方案,敬请期待,也欢迎加入我们的QQ交流群:592465424,期待一起交流&学习! +### 体验应用(**体验应用至少需要两台设备**) -## 效果演示 +#### 用户 A -### 实时视频通话 +步骤1、输入用户名(请确保用户名唯一性,不能与其他用户重复),如图示: - - - - - - - - - -
主动呼叫被叫接听
+ -### 实时语音通话 +步骤2、输入要拨打的用户名,点击搜索,如下图示: - - - - - - - - - -
主动呼叫被叫接听
+ -## Demo 体验 +步骤3、点击呼叫,选择拨打**语音通话**或者**视频通话**(请确保被叫方保持在应用内,否则可能会拨打失败); -| iOS | Android | 小程序 | -| ----------------------------- | ----------------------------- | ----------------------------- | -| ![](https://liteav.sdk.qcloud.com/doc/res/trtc/picture/zh-cn/app_download_ios.png) | ![](https://liteav.sdk.qcloud.com/doc/res/trtc/picture/zh-cn/app_download_android.png) | ![](https://sdk-web-1252463788.cos.ap-hongkong.myqcloud.com/component/miniApp/QRcode/tencentTRTC.jpg) | + -## 文档资源 +#### 用户 B -| iOS | Android | 小程序 | -| ----------------------------- | ----------------------------- | ----------------------------- | -| [实时视频通话(iOS)](https://cloud.tencent.com/document/product/647/42044) | [实时视频通话(Android)](https://cloud.tencent.com/document/product/647/42045) | [实时视频通话(小程序)](https://cloud.tencent.com/document/product/647/49379) | -| [实时语音通话(iOS)](https://cloud.tencent.com/document/product/647/42046) | [实时语音通话(Android)](https://cloud.tencent.com/document/product/647/42047) | [实时语音通话(小程序)](https://cloud.tencent.com/document/product/647/49363) | +步骤1、输入用户名(请确保用户名唯一性,不能与其他用户重复),如图示: -## 其他 + -### 交流&反馈 +步骤2、进入主页,等待接听来电; +## 常见问题 +#### 1. 查看密钥时只能获取公钥和私钥信息,该如何获取密钥? +TRTC SDK 6.6 版本(2019年08月)开始启用新的签名算法 HMAC-SHA256。在此之前已创建的应用,需要先升级签名算法才能获取新的加密密钥。如不升级,您也可以继续使用 [老版本算法 ECDSA-SHA256](https://cloud.tencent.com/document/product/647/17275#.E8.80.81.E7.89.88.E6.9C.AC.E7.AE.97.E6.B3.95),如已升级,您按需切换为新旧算法。 -欢迎加入QQ群进行技术交流和反馈问题,QQ群:592465424 +升级/切换操作: -![image-20210622142449407](https://main.qcloudimg.com/raw/1ea3ab1ff36d37c889f4140499585a4a.png) + 1. 登录 [实时音视频控制台](https://console.cloud.tencent.com/trtc)。 + 2. 在左侧导航栏选择【应用管理】,单击目标应用所在行的【应用信息】。 + 3. 选择【快速上手】页签,单击【第二步 获取签发UserSig的密钥】区域的【点此升级】、【非对称式加密】或【HMAC-SHA256】。 + + - 升级: + + ![](https://main.qcloudimg.com/raw/69bd0957c99e6a6764368d7f13c6a257.png) + + - 切换回老版本算法 ECDSA-SHA256: + + ![](https://main.qcloudimg.com/raw/f89c00f4a98f3493ecc1fe89bea02230.png) + + - 切换为新版本算法 HMAC-SHA256: + + ![](https://main.qcloudimg.com/raw/b0412153935704abc9e286868ad8a916.png) + -### 更多信息 +#### 2. 两台手机同时运行工程,为什么看不到彼此的画面? +请确保两台手机在运行工程时使用的是不同的 UserID,TRTC 不支持同一个 UserID (除非 SDKAppID 不同)在两个终端同时使用。 + -访问 Github 较慢的客户可以考虑使用国内下载地址,腾讯云提供有全平台等解决方案,更多信息详见[腾讯云TRTC - SDK 下载](https://cloud.tencent.com/document/product/647/32689) 。 +#### 3. 防火墙有什么限制? +由于 SDK 使用 UDP 协议进行音视频传输,所以在对 UDP 有拦截的办公网络下无法使用。如遇到类似问题,请参考 [应对公司防火墙限制](https://cloud.tencent.com/document/product/647/34399) 排查并解决。 -| 所属平台 | Zip下载 | SDK集成指引 | API 列表 | -|:---------:| :--------:|:--------:|:--------:| -| iOS | [下载](https://liteav.sdk.qcloud.com/download/latest/TXLiteAVSDK_TRTC_iOS_latest.zip)|[DOC](https://cloud.tencent.com/document/product/647/32173) | [API](https://cloud.tencent.com/document/product/647/32258) | -| Android | [下载](https://liteav.sdk.qcloud.com/download/latest/TXLiteAVSDK_TRTC_Android_latest.zip)| [DOC](https://cloud.tencent.com/document/product/647/32175) | [API](https://cloud.tencent.com/document/product/647/32267) | -| Win(C++)| [下载](https://liteav.sdk.qcloud.com/download/latest/TXLiteAVSDK_TRTC_Win_latest.zip)| [DOC](https://cloud.tencent.com/document/product/647/32178) | [API](https://cloud.tencent.com/document/product/647/32268) | -| Win(C#)| [下载](https://liteav.sdk.qcloud.com/download/latest/TXLiteAVSDK_TRTC_Win_latest.zip)| [DOC](https://cloud.tencent.com/document/product/647/32178) | [API](https://cloud.tencent.com/document/product/647/36776) | -| Mac| [下载](https://liteav.sdk.qcloud.com/download/latest/TXLiteAVSDK_TRTC_Mac_latest.tar.bz2)| [DOC](https://cloud.tencent.com/document/product/647/32176) |[API](https://cloud.tencent.com/document/product/647/32258) | -| Web | [下载](https://web.sdk.qcloud.com/trtc/webrtc/download/webrtc_latest.zip)| [DOC](https://cloud.tencent.com/document/product/647/16863) |[API](https://cloud.tencent.com/document/product/647/17249) | -| Electron | [下载](https://web.sdk.qcloud.com/trtc/electron/download/TXLiteAVSDK_TRTC_Electron_latest.zip) | [DOC](https://cloud.tencent.com/document/product/647/38549) |[API](https://cloud.tencent.com/document/product/647/38551) | -| 微信小程序 | [下载](https://web.sdk.qcloud.com/component/trtccalling/download/trtc-calling-miniapp.zip) | [DOC](https://cloud.tencent.com/document/product/647/32183) |[API](https://cloud.tencent.com/document/product/647/17018) | \ No newline at end of file + + + diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/AudioFile/phone_dialing.m4a b/iOS/TUIKit/TUICalling/Resources/AudioFile/phone_dialing.m4a similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/AudioFile/phone_dialing.m4a rename to iOS/TUIKit/TUICalling/Resources/AudioFile/phone_dialing.m4a diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/AudioFile/phone_hangup.mp3 b/iOS/TUIKit/TUICalling/Resources/AudioFile/phone_hangup.mp3 similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/AudioFile/phone_hangup.mp3 rename to iOS/TUIKit/TUICalling/Resources/AudioFile/phone_hangup.mp3 diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/AudioFile/phone_ringing.flac b/iOS/TUIKit/TUICalling/Resources/AudioFile/phone_ringing.flac similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/AudioFile/phone_ringing.flac rename to iOS/TUIKit/TUICalling/Resources/AudioFile/phone_ringing.flac diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/calling_loading.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/calling_loading.imageset/Contents.json new file mode 100644 index 000000000..f6a3405e0 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/calling_loading.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "calling_loading.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/calling_loading.imageset/calling_loading.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/calling_loading.imageset/calling_loading.png new file mode 100644 index 000000000..0579166db Binary files /dev/null and b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/calling_loading.imageset/calling_loading.png differ diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/calling_mic.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/calling_mic.imageset/Contents.json similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/calling_mic.imageset/Contents.json rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/calling_mic.imageset/Contents.json diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/calling_mic.imageset/calling_mic@2x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/calling_mic.imageset/calling_mic@2x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/calling_mic.imageset/calling_mic@2x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/calling_mic.imageset/calling_mic@2x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/calling_mic.imageset/calling_mic@3x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/calling_mic.imageset/calling_mic@3x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/calling_mic.imageset/calling_mic@3x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/calling_mic.imageset/calling_mic@3x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/camera_off.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/camera_off.imageset/Contents.json similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/camera_off.imageset/Contents.json rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/camera_off.imageset/Contents.json diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/camera_off.imageset/camera_off@2x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/camera_off.imageset/camera_off@2x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/camera_off.imageset/camera_off@2x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/camera_off.imageset/camera_off@2x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/camera_off.imageset/camera_off@3x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/camera_off.imageset/camera_off@3x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/camera_off.imageset/camera_off@3x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/camera_off.imageset/camera_off@3x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/camera_on.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/camera_on.imageset/Contents.json similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/camera_on.imageset/Contents.json rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/camera_on.imageset/Contents.json diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/camera_on.imageset/camera_on@2x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/camera_on.imageset/camera_on@2x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/camera_on.imageset/camera_on@2x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/camera_on.imageset/camera_on@2x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/camera_on.imageset/camera_on@3x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/camera_on.imageset/camera_on@3x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/camera_on.imageset/camera_on@3x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/camera_on.imageset/camera_on@3x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_handsfree.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_handsfree.imageset/Contents.json similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_handsfree.imageset/Contents.json rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_handsfree.imageset/Contents.json diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_handsfree.imageset/ic_handsfree@2x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_handsfree.imageset/ic_handsfree@2x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_handsfree.imageset/ic_handsfree@2x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_handsfree.imageset/ic_handsfree@2x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_handsfree.imageset/ic_handsfree@3x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_handsfree.imageset/ic_handsfree@3x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_handsfree.imageset/ic_handsfree@3x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_handsfree.imageset/ic_handsfree@3x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_handsfree_on.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_handsfree_on.imageset/Contents.json similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_handsfree_on.imageset/Contents.json rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_handsfree_on.imageset/Contents.json diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@2x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@2x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@2x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@2x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@3x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@3x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@3x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@3x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_hangup.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_hangup.imageset/Contents.json similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_hangup.imageset/Contents.json rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_hangup.imageset/Contents.json diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_hangup.imageset/ic_hangup@2x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_hangup.imageset/ic_hangup@2x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_hangup.imageset/ic_hangup@2x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_hangup.imageset/ic_hangup@2x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_hangup.imageset/ic_hangup@3x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_hangup.imageset/ic_hangup@3x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_hangup.imageset/ic_hangup@3x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_hangup.imageset/ic_hangup@3x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_mute.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_mute.imageset/Contents.json similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_mute.imageset/Contents.json rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_mute.imageset/Contents.json diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_mute.imageset/ic_mute@2x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_mute.imageset/ic_mute@2x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_mute.imageset/ic_mute@2x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_mute.imageset/ic_mute@2x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_mute.imageset/ic_mute@3x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_mute.imageset/ic_mute@3x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_mute.imageset/ic_mute@3x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_mute.imageset/ic_mute@3x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_mute_on.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_mute_on.imageset/Contents.json similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_mute_on.imageset/Contents.json rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_mute_on.imageset/Contents.json diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_mute_on.imageset/ic_mute_on@2x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_mute_on.imageset/ic_mute_on@2x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_mute_on.imageset/ic_mute_on@2x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_mute_on.imageset/ic_mute_on@2x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_mute_on.imageset/ic_mute_on@3x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_mute_on.imageset/ic_mute_on@3x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/ic_mute_on.imageset/ic_mute_on@3x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/ic_mute_on.imageset/ic_mute_on@3x.png diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-1.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-1.imageset/Contents.json new file mode 100644 index 000000000..8ec743139 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "loading-1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-1.imageset/loading-1.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-1.imageset/loading-1.png new file mode 100644 index 000000000..3f3425b99 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-1.imageset/loading-1.png differ diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-2.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-2.imageset/Contents.json new file mode 100644 index 000000000..7d72b5683 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "loading-2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-2.imageset/loading-2.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-2.imageset/loading-2.png new file mode 100644 index 000000000..d9d44aacf Binary files /dev/null and b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-2.imageset/loading-2.png differ diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-3.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-3.imageset/Contents.json new file mode 100644 index 000000000..47ace1985 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "loading-3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-3.imageset/loading-3.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-3.imageset/loading-3.png new file mode 100644 index 000000000..87ba8d48f Binary files /dev/null and b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-3.imageset/loading-3.png differ diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-4.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-4.imageset/Contents.json new file mode 100644 index 000000000..a874923ba --- /dev/null +++ b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "loading-4.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-4.imageset/loading-4.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-4.imageset/loading-4.png new file mode 100644 index 000000000..98d872e16 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-4.imageset/loading-4.png differ diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-5.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-5.imageset/Contents.json new file mode 100644 index 000000000..e67b67d06 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "loading-5.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-5.imageset/loading-5.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-5.imageset/loading-5.png new file mode 100644 index 000000000..3c3d36567 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-5.imageset/loading-5.png differ diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-6.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-6.imageset/Contents.json new file mode 100644 index 000000000..c58711e16 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-6.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "loading-6.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-6.imageset/loading-6.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-6.imageset/loading-6.png new file mode 100644 index 000000000..265489989 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-6.imageset/loading-6.png differ diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-7.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-7.imageset/Contents.json new file mode 100644 index 000000000..efdb289cf --- /dev/null +++ b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-7.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "loading-7.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-7.imageset/loading-7.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-7.imageset/loading-7.png new file mode 100644 index 000000000..b7086cb05 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-7.imageset/loading-7.png differ diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-8.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-8.imageset/Contents.json new file mode 100644 index 000000000..537853e12 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-8.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "loading-8.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-8.imageset/loading-8.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-8.imageset/loading-8.png new file mode 100644 index 000000000..dc50bf47b Binary files /dev/null and b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/loading-8.imageset/loading-8.png differ diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/switch2audio.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/switch2audio.imageset/Contents.json similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/switch2audio.imageset/Contents.json rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/switch2audio.imageset/Contents.json diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/switch2audio.imageset/switch2audio@2x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/switch2audio.imageset/switch2audio@2x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/switch2audio.imageset/switch2audio@2x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/switch2audio.imageset/switch2audio@2x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/switch2audio.imageset/switch2audio@3x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/switch2audio.imageset/switch2audio@3x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/switch2audio.imageset/switch2audio@3x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/switch2audio.imageset/switch2audio@3x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/switch_camera.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/switch_camera.imageset/Contents.json similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/switch_camera.imageset/Contents.json rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/switch_camera.imageset/Contents.json diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/switch_camera.imageset/switch_camera@2x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/switch_camera.imageset/switch_camera@2x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/switch_camera.imageset/switch_camera@2x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/switch_camera.imageset/switch_camera@2x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/switch_camera.imageset/switch_camera@3x.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/switch_camera.imageset/switch_camera@3x.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/switch_camera.imageset/switch_camera@3x.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/switch_camera.imageset/switch_camera@3x.png diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/trtccalling_ic_dialing.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/trtccalling_ic_dialing.imageset/Contents.json similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/trtccalling_ic_dialing.imageset/Contents.json rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/trtccalling_ic_dialing.imageset/Contents.json diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/trtccalling_ic_dialing.imageset/trtccalling_ic_dialing.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/trtccalling_ic_dialing.imageset/trtccalling_ic_dialing.png similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Resources/Calling.xcassets/trtccalling_ic_dialing.imageset/trtccalling_ic_dialing.png rename to iOS/TUIKit/TUICalling/Resources/Calling.xcassets/trtccalling_ic_dialing.imageset/trtccalling_ic_dialing.png diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/userIcon.imageset/Contents.json b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/userIcon.imageset/Contents.json new file mode 100644 index 000000000..0e1096fdd --- /dev/null +++ b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/userIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "userIcon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/userIcon.imageset/userIcon.png b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/userIcon.imageset/userIcon.png new file mode 100644 index 000000000..6d3b7eca6 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Resources/Calling.xcassets/userIcon.imageset/userIcon.png differ diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Localized/en.lproj/CallingLocalized.strings b/iOS/TUIKit/TUICalling/Resources/Localized/en.lproj/CallingLocalized.strings similarity index 94% rename from iOS/TUIKit/TUICalling/iOS/Resources/Localized/en.lproj/CallingLocalized.strings rename to iOS/TUIKit/TUICalling/Resources/Localized/en.lproj/CallingLocalized.strings index 380107675..ce02ff1d5 100644 --- a/iOS/TUIKit/TUICalling/iOS/Resources/Localized/en.lproj/CallingLocalized.strings +++ b/iOS/TUIKit/TUICalling/Resources/Localized/en.lproj/CallingLocalized.strings @@ -18,7 +18,7 @@ "Demo.TRTC.calling.searching" = "Search"; "Demo.TRTC.calling.searchandcall" = "Search for a registered user\n to start a call"; "Demo.TRTC.calling.callingbegan" = "Joined call"; -"Demo.TRTC.calling.callingcancel" = "Call cancelled"; +"Demo.TRTC.calling.callingcancel" = "Cancelled the call"; "Demo.TRTC.calling.callingtimeout" = "Call timed out"; "Demo.TRTC.calling.callingrefuse" = "declined the call"; "Demo.TRTC.calling.callingleave" = "left the call"; @@ -34,7 +34,7 @@ "LoginNetwork.AppUtils.tomeettheregulatory" = "In accordance with relevant regulations, the app forbids pornographic, abusive, violent, terrorist, and politically sensitive content. All audio/video calls and live streams are recorded and archived."; "LoginNetwork.AppUtils.determine" = "OK"; "Demo.TRTC.calling.invitetovideocall" = "invites you to a video call"; -"Demo.TRTC.calling.invitetoaudiocall" = "invites you to a audio call"; +"Demo.TRTC.calling.invitetoaudiocall" = "invites you to a voice call"; "Demo.TRTC.Streaming.call" = "Call"; "Demo.TRTC.Calling.waitaccept" = "Waiting for the user to accept..."; @@ -43,7 +43,7 @@ "Demo.TRTC.Calling.mic" = "Mic"; "Demo.TRTC.Calling.speaker" = "Speaker"; "Demo.TRTC.Calling.camera" = "Camera"; -"Demo.TRTC.Calling.switchtoaudio" = "Switch to Audio Call"; +"Demo.TRTC.Calling.switchtoaudio" = "Switch to Voice Call"; "Demo.TRTC.Calling.answer" = "Answer"; "Demo.TRTC.Calling.othernetworkpoor" = "The other party's network connection is poor"; "Demo.TRTC.Calling.yournetworkpoor" = "Your network connection is poor"; diff --git a/iOS/TUIKit/TUICalling/iOS/Resources/Localized/zh-Hans.lproj/CallingLocalized.strings b/iOS/TUIKit/TUICalling/Resources/Localized/zh-Hans.lproj/CallingLocalized.strings similarity index 98% rename from iOS/TUIKit/TUICalling/iOS/Resources/Localized/zh-Hans.lproj/CallingLocalized.strings rename to iOS/TUIKit/TUICalling/Resources/Localized/zh-Hans.lproj/CallingLocalized.strings index f4b4aa2aa..a03a7a6ca 100644 --- a/iOS/TUIKit/TUICalling/iOS/Resources/Localized/zh-Hans.lproj/CallingLocalized.strings +++ b/iOS/TUIKit/TUICalling/Resources/Localized/zh-Hans.lproj/CallingLocalized.strings @@ -18,7 +18,7 @@ "Demo.TRTC.calling.searching" = "搜索"; "Demo.TRTC.calling.searchandcall" = "搜索添加已注册用户\n以发起通话"; "Demo.TRTC.calling.callingbegan" = "进入通话"; -"Demo.TRTC.calling.callingcancel" = "通话取消"; +"Demo.TRTC.calling.callingcancel" = "取消了通话"; "Demo.TRTC.calling.callingtimeout" = "通话超时"; "Demo.TRTC.calling.callingrefuse" = "拒绝了通话"; "Demo.TRTC.calling.callingleave" = "离开了通话"; diff --git a/iOS/TUIKit/TUICalling/Resources/TUICallingRelease.podspec b/iOS/TUIKit/TUICalling/Resources/TUICallingRelease.podspec new file mode 100644 index 000000000..b68fbb58d --- /dev/null +++ b/iOS/TUIKit/TUICalling/Resources/TUICallingRelease.podspec @@ -0,0 +1,60 @@ +Pod::Spec.new do |spec| + spec.name = 'TUICalling' + spec.version = '9.4.0' + spec.platform = :ios + spec.ios.deployment_target = '9.0' + spec.license = { :type => 'Proprietary', + :text => <<-LICENSE + copyright 2017 tencent Ltd. All rights reserved. + LICENSE + } + spec.homepage = 'https://cloud.tencent.com/document/product/647' + spec.documentation_url = 'https://cloud.tencent.com/document/product/647' + spec.authors = 'tencent video cloud' + spec.summary = 'TUICalling' + spec.xcconfig = { 'VALID_ARCHS' => 'armv7 arm64 x86_64' } + + spec.dependency 'Masonry' + spec.dependency 'TUICore' + + spec.requires_arc = true + spec.static_framework = true + spec.source = { :git => 'https://github.com/tencentyun/TUICalling.git', :tag => "v9.4" } + spec.pod_target_xcconfig = { + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' + } + spec.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' } + spec.swift_version = '5.0' + spec.default_subspec = 'TRTC' + + + spec.subspec 'TRTC' do |trtc| + trtc.dependency 'TXLiteAVSDK_TRTC' + # framework_path="../../SDK/TXLiteAVSDK_TRTC.framework" + # trtc.pod_target_xcconfig={ + # 'HEADER_SEARCH_PATHS'=>["$(PODS_TARGET_SRCROOT)/#{framework_path}/Headers"] + # } + trtc.source_files = 'iOS/Source/localized/**/*.{h,m,mm}', 'iOS/Source/Model/**/*.{h,m,mm}', 'iOS/Source/Service/**/*.{h,m,mm}', 'iOS/Source/UI/**/*.{h,m,mm}', 'iOS/Source/TUICallingKit_TRTC/*.{h,m,mm}' + trtc.ios.framework = ['AVFoundation', 'Accelerate'] + trtc.library = 'c++', 'resolv' + trtc.resource_bundles = { + 'TUICallingKitBundle' => ['iOS/Resources/Localized/*', 'iOS/Resources/AudioFile', 'iOS/Resources/*.xcassets'] + } + end + + spec.subspec 'Professional' do |professional| + professional.dependency 'TXLiteAVSDK_Professional' + # framework_path="../../SDK/TXLiteAVSDK_Professional.framework" + # professional.pod_target_xcconfig={ + # 'HEADER_SEARCH_PATHS'=>["$(PODS_TARGET_SRCROOT)/#{framework_path}/Headers"] + # } + # professional.xcconfig = { 'HEADER_SEARCH_PATHS' => '${SRCROOT}/../SDK/TXLiteAVSDK_Professional.framework/Headers/'} + professional.source_files = 'iOS/Source/localized/**/*.{h,m,mm}', 'iOS/Source/Model/**/*.{h,m,mm}', 'iOS/Source/Service/**/*.{h,m,mm}', 'iOS/Source/UI/**/*.{h,m,mm}', 'iOS/Source/TUICallingKit_Professional/*.{h,m,mm}' + professional.ios.framework = ['AVFoundation', 'Accelerate', 'AssetsLibrary'] + professional.library = 'c++', 'resolv', 'sqlite3' + professional.resource_bundles = { + 'TUICallingKitBundle' => ['iOS/Resources/Localized/*', 'iOS/Resources/AudioFile', 'iOS/Resources/*.xcassets'] + } + end + +end diff --git a/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCalling+Signal.h b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCalling+Signal.h new file mode 100644 index 000000000..4ecb04cd7 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCalling+Signal.h @@ -0,0 +1,81 @@ +// +// TRTCCall+Signal.h +// TXIMSDK_TUIKit_iOS +// +// Created by xiangzhang on 2020/7/3. +// + +#import "TRTCCalling.h" + +@protocol TRTCCallingDelegate; + +@interface TRTCCalling (Signal) + +/// 添加信令监听 +- (void)addSignalListener; + +/// 移除信令监听 +- (void)removeSignalListener; + +/// 通过信令发起通话邀请 +- (NSString *)invite:(NSString *)receiver action:(CallAction)action model:(CallModel *)model cmdInfo:(NSString *)cmdInfo; +- (NSString *)invite:(NSString *)receiver action:(CallAction)action model:(CallModel *)model cmdInfo:(NSString *)cmdInfo userIds:(NSArray *)userIds; + +/// 收到通话邀请推送通知 +- (void)onReceiveGroupCallAPNs:(V2TIMSignalingInfo *)signalingInfo; + +/// 检查是否满足自动挂断逻辑 +/// @param leaveUser 离开的房间的用户id +- (void)preExitRoom:(NSString *)leaveUser; + +/// 发送 C2C 自定义(信令)消息 +- (void)sendInviteAction:(CallAction)action user:(NSString *)user model:(CallModel *)model; + +@end + +/// TRTCCalling扩展参数 +@interface TRTCCalling () + +/// 邀请人 ID,如果邀请失败,为nil +@property (nonatomic, copy) NSString *curCallID; +/// 不存在GroupID的处理 +@property (nonatomic, strong) NSMutableDictionary *curCallIdDic; +/// 群邀请的群 ID +@property (nonatomic, copy) NSString *curGroupID; +/// 被邀请的所有用户 ID +@property (nonatomic, strong) NSMutableArray *calleeUserIDs; +/// 记录当前正在邀请成员的列表(被叫:会拼接上主动呼叫者。。。 主叫:不包含主叫者) +@property (nonatomic, strong) NSMutableArray *curInvitingList; +/// 记录当前已经进入房间成功的成员列表 +@property (nonatomic, strong) NSMutableArray *curRoomList; +/// 对自己发起通话邀请的人 +@property (nonatomic, copy) NSString *curSponsorForMe; +/// 音视频邀请都需要展示消息,这个参数最好做成可配置,如果设置为 false 信令邀请就会产生 IM 消息 +@property (nonatomic, assign) BOOL onlineUserOnly; +/// 用于区分主叫、被叫 默认是被叫 「被叫」:YES 「主叫」:NO +@property (nonatomic, assign) BOOL isBeingCalled; +/// 记录类型 Unknown、Audio、Video ( 未知类型 、语音邀请 、视频邀请) +@property (nonatomic, assign) CallType curType; +/// 记录当前的房间ID +@property (nonatomic, assign) UInt32 curRoomID; +/// 记录「自己」是否已经进房成功 (进房成功、退房成功后 赋值) +@property (nonatomic, assign) BOOL isInRoom; +/// 记录「自己」是否正在通话中… +@property (nonatomic, assign) BOOL isOnCalling; +/// 是否为前置摄像头 +@property (nonatomic, assign) BOOL isFrontCamera; +/// 通话要计算通话时长,  记录一下 +@property (nonatomic, assign) UInt64 startCallTS; + +@property (nonatomic, strong) CallModel *curLastModel; +@property (nonatomic, strong) NSString *callID; +@property (nonatomic, copy) NSString *switchToAudioCallID; +@property (nonatomic, copy) NSString *currentCallingUserID; + +@property (nonatomic, weak) id delegate; + +- (void)enterRoom; + +- (void)quitRoom; + +@end diff --git a/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCalling+Signal.m b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCalling+Signal.m new file mode 100644 index 000000000..f7fbd977b --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCalling+Signal.m @@ -0,0 +1,876 @@ +// +// TRTCCall+Signal.m +// TXIMSDK_TUIKit_iOS +// +// Created by xiangzhang on 2020/7/3. +// + +#import "TRTCCalling+Signal.h" +#import "TRTCCallingUtils.h" +#import "TRTCCallingHeader.h" +#import +#import "CallingLocalized.h" +#import "TRTCSignalFactory.h" +#import "TUICallingConstants.h" + +@implementation TRTCCalling (Signal) + +- (void)addSignalListener { + [[V2TIMManager sharedInstance] addSignalingListener:self]; + [[V2TIMManager sharedInstance] addSimpleMsgListener:self]; +} + +- (void)removeSignalListener { + [[V2TIMManager sharedInstance] removeSignalingListener:self]; + [[V2TIMManager sharedInstance] removeSimpleMsgListener:self]; +} + +- (NSString *)invite:(NSString *)receiver action:(CallAction)action model:(CallModel *)model cmdInfo:(NSString *)cmdInfo { + return [self invite:receiver action:action model:model cmdInfo:cmdInfo userIds:nil]; +} + +- (NSString *)invite:(NSString *)receiver + action:(CallAction)action + model:(CallModel *)model + cmdInfo:(NSString *)cmdInfo + userIds:(NSArray *)userIds { + TRTCLog(@"Calling - invite receiver:%@ action:%ld cmdInfo:%@", receiver, action, cmdInfo); + NSString *callID = @""; + CallModel *realModel = [self generateModel:action]; + BOOL isGroup = [self.curGroupID isEqualToString:receiver]; + + if (model) { + realModel = [model copy]; + realModel.action = action; + if (model.groupid.length > 0) { + isGroup = YES; + } + } + + /// 邀请 ID, 说明:不存在GroupID 组通话,分成多个C to C, 在Map中存储userID 和inviteID 关系。 GroupID组 和 单个 C to C 通话,inviteID 从Model中取。 + NSString *inviteID = [self getCallIDWithUserID:receiver] ?: realModel.callid; + + switch (realModel.action) { + case CallAction_Call: { + NSString *cmd = @""; + switch (realModel.calltype) { + case CallType_Video: + cmd = SIGNALING_CMD_VIDEOCALL; + break; + case CallType_Audio: + cmd = SIGNALING_CMD_AUDIOCALL; + break; + default: + break; + } + + NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" + roomID:realModel.roomid + cmd:cmd + cmdInfo:@"" + userIds:userIds ?: @[] + message:@"" + callType:realModel.calltype]; + [dataDic setValue:@(realModel.roomid) forKey:SIGNALING_EXTRA_KEY_ROOM_ID]; + NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; + + if (isGroup) { + @weakify(self) + callID = [[V2TIMManager sharedInstance] inviteInGroup:realModel.groupid + inviteeList:realModel.invitedList + data:data + onlineUserOnly:self.onlineUserOnly + timeout:SIGNALING_EXTRA_KEY_TIME_OUT succ:^{ + TRTCLog(@"Calling - CallAction_Call inviteInGroup success"); + @strongify(self) + // 发起 Apns 推送,群组的邀请,需要单独对每个被邀请人发起推送 + for (NSString *invitee in realModel.invitedList) { + [self sendAPNsForCall:invitee + inviteeList:realModel.invitedList + callID:self.callID + groupid:realModel.groupid + roomid:realModel.roomid]; + } + } fail:^(int code, NSString *desc) { + TRTCLog(@"Calling - CallAction_Call inviteInGroup failed, code: %d desc: %@", code, desc); + @strongify(self) + if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { + [self.delegate onError:code msg:desc]; + }; + }]; + self.callID = callID; + } else { + @weakify(self) + V2TIMOfflinePushInfo *info = [self getOfflinePushInfoWithInviteeList:realModel.invitedList + callID:nil groupid:nil + roomid:realModel.roomid]; + callID = [[V2TIMManager sharedInstance] invite:receiver data:data + onlineUserOnly:self.onlineUserOnly + offlinePushInfo:info + timeout:SIGNALING_EXTRA_KEY_TIME_OUT + succ:^{ + TRTCLog(@"Calling - CallAction_Call invite success"); + } fail:^(int code, NSString *desc) { + TRTCLog(@"Calling - CallAction_Call invite failed, code: %d desc: %@", code, desc); + @strongify(self) + if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { + [self.delegate onError:code msg:desc]; + }; + }]; + self.callID = callID; + } + } break; + + case CallAction_Accept: { + NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" + roomID:realModel.roomid + cmd:@"" + cmdInfo:@"" + message:@"" + callType:realModel.calltype]; + NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; + + @weakify(self) + [[V2TIMManager sharedInstance] accept:inviteID data:data succ:^{ + TRTCLog(@"Calling - CallAction_Accept accept success"); + } fail:^(int code, NSString *desc) { + TRTCLog(@"Calling - CallAction_Accept accept failed, code: %d desc: %@", code, desc); + @strongify(self) + if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { + [self.delegate onError:code msg:desc]; + }; + }]; + } break; + + case CallAction_Reject: { + NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" + roomID:realModel.roomid + cmd:@"" + cmdInfo:@"" + message:@"" + callType:realModel.calltype]; + NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; + @weakify(self) + [[V2TIMManager sharedInstance] reject:inviteID data:data succ:^{ + TRTCLog(@"Calling - CallAction_Reject reject success"); + } fail:^(int code, NSString *desc) { + TRTCLog(@"Calling - CallAction_Reject reject failed, code: %d desc: %@", code, desc); + @strongify(self) + if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { + [self.delegate onError:code msg:desc]; + }; + }]; + } break; + + case CallAction_Linebusy: { + NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" + roomID:realModel.roomid + cmd:@"" + cmdInfo:@"" + message:SIGNALING_MESSAGE_LINEBUSY + callType:realModel.calltype]; + [dataDic setValue:SIGNALING_EXTRA_KEY_LINE_BUSY forKey:SIGNALING_EXTRA_KEY_LINE_BUSY]; + NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; + + @weakify(self) + [[V2TIMManager sharedInstance] reject:inviteID data:data succ:^{ + TRTCLog(@"Calling - CallAction_Linebusy reject success"); + } fail:^(int code, NSString *desc) { + TRTCLog(@"Calling - CallAction_Linebusy reject failed, code: %d desc: %@", code, desc); + @strongify(self) + if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { + [self.delegate onError:code msg:desc]; + }; + }]; + } break; + + case CallAction_Cancel: { + NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" + roomID:realModel.roomid + cmd:@"" + cmdInfo:@"" + message:@"" + callType:realModel.calltype]; + NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; + + @weakify(self) + [[V2TIMManager sharedInstance] cancel:inviteID data:data succ:^{ + TRTCLog(@"Calling - CallAction_Cancel cancel success"); + } fail:^(int code, NSString *desc) { + TRTCLog(@"Calling - CallAction_Cancel cancel failed, code: %d desc: %@", code, desc); + @strongify(self) + if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { + [self.delegate onError:code msg:desc]; + }; + }]; + } break; + + case CallAction_End: { + if (isGroup) { + // 群通话不需要计算通话时长 + NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" + roomID:realModel.roomid + cmd:SIGNALING_CMD_HANGUP + cmdInfo:@"0" + message:@"" + callType:realModel.calltype]; + [dataDic setValue:@(0) forKey:SIGNALING_EXTRA_KEY_CALL_END]; + NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; + // 这里发结束事件的时候,inviteeList 已经为 nil 了,可以伪造一个被邀请用户,把结束的信令发到群里展示。 + // timeout 这里传 0,结束的事件不需要做超时检测 + @weakify(self) + callID = [[V2TIMManager sharedInstance] inviteInGroup:realModel.groupid + inviteeList:@[@"inviteeList"] + data:data + onlineUserOnly:self.onlineUserOnly + timeout:0 + succ:^{ + TRTCLog(@"Calling - CallAction_End inviteInGroup success"); + } fail:^(int code, NSString *desc) { + TRTCLog(@"Calling - CallAction_End inviteInGroup failed, code: %d desc: %@", code, desc); + @strongify(self) + if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { + [self.delegate onError:code msg:desc]; + }; + }]; + } else { + NSDate *now = [NSDate date]; + NSString *cmdInfo = [NSString stringWithFormat:@"%llu", (UInt64)[now timeIntervalSince1970] - self.startCallTS]; + NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" + roomID:realModel.roomid + cmd:SIGNALING_CMD_HANGUP + cmdInfo:cmdInfo + message:@"" + callType:realModel.calltype]; + [dataDic setValue:@((UInt64)[now timeIntervalSince1970] - self.startCallTS) forKey:SIGNALING_EXTRA_KEY_CALL_END]; + NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; + @weakify(self) + callID = [[V2TIMManager sharedInstance] invite:receiver + data:data + onlineUserOnly:self.onlineUserOnly + offlinePushInfo:nil + timeout:0 + succ:^{ + TRTCLog(@"Calling - CallAction_End invite success"); + } fail:^(int code, NSString *desc) { + TRTCLog(@"Calling - CallAction_End invite failed, code: %d desc: %@", code, desc); + @strongify(self) + if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { + [self.delegate onError:code msg:desc]; + }; + }]; + self.startCallTS = 0; + } + } break; + + case CallAction_SwitchToAudio: { + NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" + roomID:realModel.roomid + cmd:SIGNALING_CMD_SWITCHTOVOICECALL + cmdInfo:@"" + message:@"" + callType:realModel.calltype]; + [dataDic setValue:SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL forKey:SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL]; + NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; + @weakify(self) + [[V2TIMManager sharedInstance] invite:receiver + data:data + onlineUserOnly:self.onlineUserOnly + offlinePushInfo:nil + timeout:SIGNALING_EXTRA_KEY_TIME_OUT + succ:^{ + TRTCLog(@"Calling - CallAction_SwitchToAudio invite success"); + } fail:^(int code, NSString *desc) { + TRTCLog(@"Calling - CallAction_SwitchToAudio invite failed, code: %d desc: %@", code, desc); + @strongify(self) + if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { + [self.delegate onError:code msg:desc]; + }; + }]; + } break; + + default: + break; + } + + if (realModel.action != CallAction_Reject && + realModel.action != CallAction_Accept && + realModel.action != CallAction_End && + realModel.action != CallAction_Cancel && + realModel.action != CallAction_SwitchToAudio && + realModel.action != CallAction_AcceptSwitchToAudio && + realModel.action != CallAction_RejectSwitchToAudio && + model == nil) { + self.curLastModel = [realModel copy]; + } + return callID; +} + +- (void)sendAPNsForCall:(NSString *)receiver + inviteeList:(NSArray *)inviteeList + callID:(NSString *)callID + groupid:(NSString *)groupid + roomid:(UInt32)roomid { + TRTCLog(@"Calling - sendAPNsForCall receiver:%@ inviteeList:%@ groupid:%@ roomid:%d", receiver, inviteeList, groupid, roomid); + if (callID.length == 0 || inviteeList.count == 0 || roomid == 0) { + TRTCLog(@"sendAPNsForCall failed"); + return; + } + V2TIMOfflinePushInfo *info = [self getOfflinePushInfoWithInviteeList:inviteeList + callID:callID + groupid:groupid + roomid:roomid]; + NSData *customMessage = [TRTCCallingUtils dictionary2JsonData:@{@"version" : @(Version) , @"businessID" : SIGNALING_BUSINESSID}]; + V2TIMMessage *msg = [[V2TIMManager sharedInstance] createCustomMessage:customMessage]; + // 针对每个被邀请成员单独邀请 + [[V2TIMManager sharedInstance] sendMessage:msg + receiver:receiver + groupID:nil + priority:V2TIM_PRIORITY_HIGH + onlineUserOnly:YES + offlinePushInfo:info + progress:nil + succ:nil + fail:nil]; +} + +- (V2TIMOfflinePushInfo *)getOfflinePushInfoWithInviteeList:(NSArray *)inviteeList + callID:(NSString *)callID + groupid:(NSString *)groupid + roomid:(UInt32)roomid{ + int chatType; //单聊:1 群聊:2 + + if (groupid.length > 0) { + chatType = 2; + } else { + chatType = 1; + groupid = @""; + } + /** + {"entity":{"version":1,"content":"{\"action\":1,\"call_type\":2,\"room_id\":804544637,\"call_id\":\"144115224095613335-1595234230-3304653590\",\"timeout\":30,\"version\":4,\"invited_list\":[\"2019\"],\"group_id\":\"@TGS#1PWYXLTGA\"}","sendTime":1595234231,"sender":"10457","chatType":2,"action":2}} + */ + NSDictionary *contentParam = @{@"action":@(SignalingActionType_Invite), + @"call_id":callID ?: @"", + @"call_type":@(self.curType), + @"invited_list":inviteeList.count > 0 ? inviteeList : @[], + @"room_id":@(roomid), + @"group_id":groupid, + @"timeout":@(SIGNALING_EXTRA_KEY_TIME_OUT), + @"version":@(Version)}; // TUIkit 业务版本 + NSDictionary *entityParam = @{@"action" : @(APNs_Business_Call), // 音视频业务逻辑推送 + @"chatType" : @(chatType), + @"content" : [TRTCCallingUtils dictionary2JsonStr:contentParam], + @"sendTime" : @((UInt32)[[V2TIMManager sharedInstance] getServerTime]), + @"sender" : [TRTCCallingUtils loginUser], + @"version" : @(APNs_Version)}; // 推送版本 + NSDictionary *extParam = @{@"entity" : entityParam}; + V2TIMOfflinePushInfo *info = [[V2TIMOfflinePushInfo alloc] init]; + info.desc = CallingLocalize(@"Demo.TRTC.calling.callingrequest"); + info.ext = [TRTCCallingUtils dictionary2JsonStr:extParam]; + info.iOSSound = @"phone_ringing.mp3"; + return info; +} + +- (void)onReceiveGroupCallAPNs:(V2TIMSignalingInfo *)signalingInfo { + if (signalingInfo.inviteID.length > 0 && + signalingInfo.inviter.length > 0 && + signalingInfo.inviteeList.count > 0 && + signalingInfo.groupID.length > 0) { + [[V2TIMManager sharedInstance] addInvitedSignaling:signalingInfo succ:^{ + [self onReceiveNewInvitation:signalingInfo.inviteID + inviter:signalingInfo.inviter + groupID:signalingInfo.groupID + inviteeList:signalingInfo.inviteeList + data:signalingInfo.data]; + } fail:^(int code, NSString *desc) { + TRTCLog(@"Calling - onReceiveAPNsForGroupCall failed,code:%d desc:%@",code,desc); + }]; + } +} + +#pragma mark - V2TIMSignalingListener + +- (void)onReceiveNewInvitation:(NSString *)inviteID + inviter:(NSString *)inviter + groupID:(NSString *)groupID + inviteeList:(NSArray *)inviteeList + data:(NSString *)data { + TRTCLog(@"Calling - onReceiveNewInvitation inviteID:%@ inviter:%@ inviteeList:%@ data:%@", inviteID, inviter, inviteeList, data); + NSDictionary *param = [self check:data]; + + if (param) { + self.isBeingCalled = YES; + NSString *cmdInfoStr = @""; + NSArray *userIds = nil; + + if ([param.allKeys containsObject:SIGNALING_EXTRA_KEY_DATA]) { + NSDictionary *data = param[SIGNALING_EXTRA_KEY_DATA]; + if ([data.allKeys containsObject:SIGNALING_EXTRA_KEY_CMDINFO]) { + cmdInfoStr = data[SIGNALING_EXTRA_KEY_CMDINFO]; + } + if ([data.allKeys containsObject:SIGNALING_EXTRA_KEY_USERIDS]) { + userIds = data[SIGNALING_EXTRA_KEY_USERIDS]; + } + } + + NSDictionary *data = [TRTCSignalFactory getDataDictionary:param]; + CallModel *model = [[CallModel alloc] init]; + model.callid = inviteID; + model.groupid = groupID; + model.inviter = inviter; + model.invitedList = [NSMutableArray arrayWithArray:inviteeList]; + model.calltype = [TRTCSignalFactory convertCmdToCallType:data[SIGNALING_EXTRA_KEY_CMD]]; + model.roomid = [data[SIGNALING_EXTRA_KEY_ROOMID] intValue]; + + if ([data[SIGNALING_EXTRA_KEY_CMD] isEqualToString:SIGNALING_CMD_SWITCHTOVOICECALL]) { + model.action = CallAction_SwitchToAudio; + self.switchToAudioCallID = inviteID; + } else { + self.currentCallingUserID = inviter; + model.action = CallAction_Call; + } + [self handleCallModel:inviter model:model message:cmdInfoStr userIds:userIds]; + } +} + +- (void)onInvitationCancelled:(NSString *)inviteID inviter:(NSString *)inviter data:(NSString *)data { + TRTCLog(@"Calling - onInvitationCancelled inviteID:%@ inviter:%@ data:%@", inviteID, inviter, data); + NSDictionary *param = [self check:data]; + + /// 多端登录,A1和A2同时登录账号A,账号B呼叫账号A ,账号B取消通话 + if (inviter && [inviter isKindOfClass:[NSString class]] && [inviter isEqualToString:[V2TIMManager sharedInstance].getLoginUser]) { + return; + } + + if (param) { + CallModel *model = [[CallModel alloc] init]; + model.callid = inviteID; + model.action = CallAction_Cancel; + [self handleCallModel:inviter model:model message:@""]; + } +} + +- (void)onInviteeAccepted:(NSString *)inviteID invitee:(NSString *)invitee data:(NSString *)data { + TRTCLog(@"Calling - onInviteeAccepted inviteID:%@ invitee:%@ data:%@", inviteID, invitee, data); + NSDictionary *param = [self check:data]; + + if (!(param && [param isKindOfClass:[NSDictionary class]])) { + return; + } + + [TRTCCloud sharedInstance].delegate = self; + CallModel *model = [[CallModel alloc] init]; + model.callid = inviteID; + NSDictionary *paramData = [TRTCSignalFactory getDataDictionary:param]; + + if ([paramData[SIGNALING_EXTRA_KEY_CMD] isEqualToString:SIGNALING_CMD_SWITCHTOVOICECALL]) { + model.action = CallAction_AcceptSwitchToAudio; + } else { + model.action = CallAction_Accept; + } + + [self handleCallModel:invitee model:model message:@""]; +} + +- (void)onInviteeRejected:(NSString *)inviteID invitee:(NSString *)invitee data:(NSString *)data { + TRTCLog(@"Calling - onInviteeRejected inviteID:%@ invitee:%@ data:%@", inviteID, invitee, data); + NSDictionary *param = [self check:data]; + + if (param) { + CallModel *model = [[CallModel alloc] init]; + model.callid = inviteID; + NSDictionary *data = [TRTCSignalFactory getDataDictionary:param]; + if ([data[SIGNALING_EXTRA_KEY_MESSAGE] isEqualToString:SIGNALING_MESSAGE_LINEBUSY]) { + model.action = CallAction_Linebusy; + } + else if ([data[SIGNALING_EXTRA_KEY_CMD] isEqualToString:SIGNALING_CMD_SWITCHTOVOICECALL]) { + model.action = CallAction_AcceptSwitchToAudio; + } + else { + model.action = CallAction_Reject; + } + [self handleCallModel:invitee model:model message:@"Other status error"]; + } +} + +- (void)onInvitationTimeout:(NSString *)inviteID inviteeList:(NSArray *)invitedList { + TRTCLog(@"Calling - onInvitationTimeout inviteID:%@ invitedList:%@", inviteID, invitedList); + CallModel *model = [[CallModel alloc] init]; + model.callid = inviteID; + model.invitedList = [NSMutableArray arrayWithArray:invitedList]; + model.action = CallAction_Timeout; + [self handleCallModel:[invitedList firstObject] model:model message:@"Timeout"]; +} + +- (NSDictionary *)check:(NSString *)data { + NSDictionary *signalingDictionary = [TRTCCallingUtils jsonSring2Dictionary:data]; + + if(!signalingDictionary[SIGNALING_EXTRA_KEY_PLATFORM] || !signalingDictionary[SIGNALING_EXTRA_KEY_DATA]) { + signalingDictionary = [TRTCSignalFactory convertOldSignalingToNewSignaling:signalingDictionary]; + } + + if (!signalingDictionary[SIGNALING_EXTRA_KEY_BUSINESSID] || + ![signalingDictionary[SIGNALING_EXTRA_KEY_BUSINESSID] isKindOfClass:[NSString class]] || + ![signalingDictionary[SIGNALING_EXTRA_KEY_BUSINESSID] isEqualToString:SIGNALING_BUSINESSID]) { + return nil; + } + + NSInteger version = [signalingDictionary[SIGNALING_EXTRA_KEY_VERSION] integerValue]; + if (version > Version) { + return nil; + } + + NSDictionary *dataDictionary = [TRTCSignalFactory getDataDictionary:signalingDictionary]; + if (dataDictionary[SIGNALING_EXTRA_KEY_CMD] && + [dataDictionary[SIGNALING_EXTRA_KEY_CMD] isKindOfClass:[NSString class]] && + [dataDictionary[SIGNALING_EXTRA_KEY_CMD] isEqualToString:SIGNALING_CMD_HANGUP]) { + // 结束的事件只用于 UI 展示通话时长,不参与业务逻辑的处理 + return nil; + } + return signalingDictionary; +} + +- (void)handleCallModel:(NSString *)user model:(CallModel *)model message:(NSString *)message { + [self handleCallModel:user model:model message:message userIds:nil]; +} + +- (void)handleCallModel:(NSString *)user model:(CallModel *)model message:(NSString *)message userIds:(NSArray *)userIds { + TRTCLog(@"Calling - handleCallModel user:%@ model:%@ message:%@ userIds:%@", user, model, message, userIds); + BOOL checkCallID = [self.curCallID isEqualToString:model.callid] || [[self getCallIDWithUserID:user] isEqualToString:model.callid]; + + switch (model.action) { + case CallAction_Call: { + [self sendInviteAction:CallAction_Call user:user model:model]; + + void(^syncInvitingList)(void) = ^(){ + for (NSString *invitee in model.invitedList) { + if (![self.curInvitingList containsObject:invitee]) { + [self.curInvitingList addObject:invitee]; + } + } + }; + + if (model.groupid != nil && ![model.invitedList containsObject:[TRTCCallingUtils loginUser]] + ) { // 群聊但是邀请不包含自己不处理 + if (self.curCallID == model.callid) { // 在房间中更新列表 + syncInvitingList(); + if (self.curInvitingList.count > 0) { + if ([self canDelegateRespondMethod:@selector(onGroupCallInviteeListUpdate:)]) { + [self.delegate onGroupCallInviteeListUpdate:self.curInvitingList]; + } + } + } + return; + } + + if (self.isOnCalling) { // tell busy + if (!checkCallID) { + BOOL isGroup = (model.groupid && model.groupid > 0); + [self invite:isGroup ? model.groupid : user action:CallAction_Linebusy model:model cmdInfo:nil]; + } + } else { + self.isOnCalling = true; + self.curCallID = model.callid; + self.curRoomID = model.roomid; + if (model.groupid.length > 0) { + self.curGroupID = model.groupid; + } + self.curType = model.calltype; + self.curSponsorForMe = user; + syncInvitingList(); + + if ([self canDelegateRespondMethod:@selector(onInvited:userIds:isFromGroup:callType:)]) { + NSMutableArray *userIdAry = model.invitedList; + [userIds enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + if (![userIdAry containsObject:obj]) { + [userIdAry addObject:obj]; + } + }]; + + [self.delegate onInvited:user userIds:userIdAry isFromGroup:self.curGroupID.length > 0 ? YES : NO callType:model.calltype]; + } + } + } break; + + case CallAction_Cancel: { + [self sendInviteAction:CallAction_Cancel user:user model:model]; + + if (checkCallID && self.delegate) { + [self preExitRoom]; + self.isOnCalling = NO; + [self.delegate onCallingCancel:user]; + } + } break; + + case CallAction_Reject: { + [self sendInviteAction:CallAction_Reject user:user model:model]; + + if (checkCallID && self.delegate) { + if ([self.curInvitingList containsObject:user]) { + [self.curInvitingList removeObject:user]; + } + if ([self canDelegateRespondMethod:@selector(onReject:)]) { + [self.delegate onReject:user]; + } + [self preExitRoom]; + } + } break; + + case CallAction_Timeout: { + [self sendInviteAction:CallAction_Timeout user:user model:model]; + + if (checkCallID && self.delegate) { + // 这里需要判断下是否是自己超时了,自己超时,直接退出界面 + if ([model.invitedList containsObject:[TRTCCallingUtils loginUser]] && self.delegate) { + self.isOnCalling = false; + if ([self canDelegateRespondMethod:@selector(onCallingTimeOut)]) { + [self.delegate onCallingTimeOut]; + } + } else { + for (NSString *userID in model.invitedList) { + if ([self canDelegateRespondMethod:@selector(onNoResp:)]) { + [self.delegate onNoResp:userID]; + } + if ([self.curInvitingList containsObject:userID]) { + [self.curInvitingList removeObject:userID]; + } + } + } + // 每次超时都需要判断当前是否需要结束通话 + [self preExitRoom]; + } + } break; + + case CallAction_Linebusy: { + TRTCLog(@"Calling - CallAction_Linebusy_out user:%@ userIds:%@ curCallID:%@ model: %@", user, userIds, self.curCallID, model); + [self sendInviteAction:CallAction_Linebusy user:user model:model]; + + if (checkCallID && self.delegate) { + TRTCLog(@"Calling - CallAction_Linebusy_in user:%@ userIds:%@", user, userIds); + if ([self.curInvitingList containsObject:user]) { + [self.curInvitingList removeObject:user]; + } + [self.delegate onLineBusy:user]; + [self preExitRoom]; + } + } break; + + case CallAction_Error: { + [self sendInviteAction:CallAction_Error user:user model:model]; + + if (checkCallID && self.delegate) { + if ([self.curInvitingList containsObject:user]) { + [self.curInvitingList removeObject:user]; + } + [self.delegate onError:-1 msg:CallingLocalize(@"Demo.TRTC.calling.syserror")]; + [self preExitRoom]; + } + } break; + + case CallAction_SwitchToAudio: { + if (!self.switchToAudioCallID) { + break; + } + int res = [self checkAudioStatus]; + if (res == 0) { + NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" + roomID:0 + cmd:SIGNALING_CMD_SWITCHTOVOICECALL + cmdInfo:@"" + message:@"" + callType:CallType_Video]; + [dataDic setValue:@(1) forKey:SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL]; + NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; + @weakify(self) + [[V2TIMManager sharedInstance] accept:self.switchToAudioCallID data:data succ:^{ + TRTCLog(@"res==0 - accept success"); + @strongify(self) + if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { + [self.delegate onSwitchToAudio:YES message:@""]; + } + } fail:^(int code, NSString *desc) { + TRTCLog(@"res==0 - accept failed, code: %d desc: %@",code,desc); + @strongify(self) + if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { + [self.delegate onSwitchToAudio:NO message:desc]; + } + }]; + } else { + NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" + roomID:0 + cmd:@"" + cmdInfo:@"" + message:@"" + callType:CallType_Video]; + [dataDic setValue:@(0) forKey:SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL]; + NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; + [[V2TIMManager sharedInstance] reject:self.switchToAudioCallID data:data succ:^{ + TRTCLog(@"res!=0 - reject success"); + } fail:^(int code, NSString *desc) { + TRTCLog(@"res!=0 - reject failed, code: %d desc: %@",code,desc); + }]; + if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { + [self.delegate onSwitchToAudio:NO message:@"Local status error"]; + } + } + } break; + + case CallAction_AcceptSwitchToAudio: { + if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { + [self.delegate onSwitchToAudio:YES message:@""]; + } + } break; + + case CallAction_RejectSwitchToAudio: { + if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { + [self.delegate onSwitchToAudio:NO message:message]; + } + } break; + + default: { + TRTCLog(@"Default CallAction"); + } break; + } +} + +#pragma mark - V2TIMSimpleMsgListener + +/// 收到 C2C 自定义(信令)消息 +- (void)onRecvC2CCustomMessage:(NSString *)msgID sender:(V2TIMUserInfo *)info customData:(NSData *)data { + TRTCLog(@"Calling - onRecvC2CCustomMessage inviteID:%@ inviter:%@", msgID, data); + if (!self.isBeingCalled) return; + + NSDictionary *param = [self check:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; + + if (!param || ![param isKindOfClass:[NSDictionary class]]) { + return; + } + + NSDictionary *dataDic; + NSString *cmdStr; + + if ([param.allKeys containsObject:SIGNALING_EXTRA_KEY_DATA] ) { + dataDic = param[SIGNALING_EXTRA_KEY_DATA]; + + if ([dataDic.allKeys containsObject:SIGNALING_EXTRA_KEY_CMD]) { + cmdStr = dataDic[SIGNALING_EXTRA_KEY_CMD]; + } + } + + if (![cmdStr isEqualToString:@"sync_info"]) return; + + CallModel *model = [[CallModel alloc] init]; + model.callid = param[SIGNALING_CUSTOM_CALLID]; + model.inviter = param[SIGNALING_CUSTOM_USER]; + model.action = [param[SIGNALING_CUSTOM_CALL_ACTION] integerValue]; + model.roomid = [dataDic[SIGNALING_EXTRA_KEY_ROOMID] intValue]; + + if (model.inviter && model.action == CallAction_Timeout) { + model.invitedList = [@[model.inviter] mutableCopy]; + } + + [self handleCallModel:model.inviter model:model message:@"" userIds:nil]; +} + +#pragma mark - Utils + +- (CallModel *)generateModel:(CallAction)action { + CallModel *model = [self.curLastModel copy]; + model.action = action; + return model; +} + +- (void)preExitRoom { + [self preExitRoom:nil]; +} + +- (void)preExitRoom:(NSString *)leaveUser { + if (!self.isInRoom && self.curInvitingList.count == 0) { + [self exitRoom]; + return; + } + + // 当前房间中存在成员,不能自动退房 + if (self.curRoomList.count > 0) return; + + if (self.curGroupID.length > 0) { + // IM 多人通话逻辑 + if (self.curInvitingList.count == 0) { + if (leaveUser) { + [self invite:@"" action:CallAction_End model:nil cmdInfo:nil]; + } + [self exitRoom]; + } + return; + } + + // C2C多人通话 和 单人通话 逻辑 + if (self.curInvitingList.count >= 1) { + return; + } + if (leaveUser) { + [self invite:leaveUser action:CallAction_End model:nil cmdInfo:nil]; + } + [self exitRoom]; +} + +- (void)exitRoom { + TRTCLog(@"Calling - autoHangUp"); + if ([self canDelegateRespondMethod:@selector(onCallEnd)]) { + [self.delegate onCallEnd]; + } + [self quitRoom]; + self.isOnCalling = NO; +} + +#pragma mark - private method + +- (void)sendInviteAction:(CallAction)action user:(NSString *)user model:(CallModel *)model { + BOOL isGroupidCall = (model.groupid && model.groupid > 0); + + if (self.isBeingCalled || isGroupidCall || [user isEqualToString:[[V2TIMManager sharedInstance] getLoginUser]]) { + return; + } + + @weakify(self) + [self.calleeUserIDs enumerateObjectsUsingBlock:^(NSString * _Nonnull calleeUserID, NSUInteger idx, BOOL * _Nonnull stop) { + if (![calleeUserID isEqualToString:user]) { + @strongify(self) + + NSString *callid = [self getCallIDWithUserID:calleeUserID]; + + if (callid && callid.length > 0) { + NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" + roomID:model.roomid + cmd:@"sync_info" + cmdInfo:@"" + message:@"" + callType:model.calltype]; + [dataDic setValue:@(action) forKey:SIGNALING_CUSTOM_CALL_ACTION]; + [dataDic setValue:callid forKey:SIGNALING_CUSTOM_CALLID]; + [dataDic setValue:user ?: @"" forKey:SIGNALING_CUSTOM_USER]; + [[V2TIMManager sharedInstance] sendC2CCustomMessage:[TRTCCallingUtils dictionary2JsonData:dataDic] to:calleeUserID succ:^{ + TRTCLog(@"Calling - sendC2CCustomMessage success %@ %@", dataDic, calleeUserID); + } fail:^(int code, NSString *desc) { + TRTCLog(@"Calling - sendC2CCustomMessage failed, code: %d desc: %@", code, desc); + }]; + } else { + TRTCLog(@"Calling - sendInviteAction callid error"); + } + } + }]; +} + +- (BOOL)canDelegateRespondMethod:(SEL)selector { + return self.delegate && [self.delegate respondsToSelector:selector]; +} + +- (NSString *)getCallIDWithUserID:(NSString *)userID { + if (userID && userID.length > 0 && [self.curCallIdDic.allKeys containsObject:userID]) { + return [self.curCallIdDic objectForKey:userID]; + } else { + return nil; + } +} + +@end diff --git a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCalling.m b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCalling.m similarity index 69% rename from iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCalling.m rename to iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCalling.m index c9173e93a..3c7707774 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCalling.m +++ b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCalling.m @@ -11,11 +11,12 @@ #import "TRTCCallingHeader.h" #import "CallingLocalized.h" #import "TUILogin.h" +#import "TUICallingConstants.h" @interface TRTCCalling () -@property(nonatomic,assign) BOOL isMicMute; -@property(nonatomic,assign) BOOL isHandsFreeOn; +@property (nonatomic, assign) BOOL isMicMute; +@property (nonatomic, assign) BOOL isHandsFreeOn; @end @@ -24,7 +25,7 @@ NSString *_curCallID; } -+(TRTCCalling *)shareInstance { ++ (TRTCCalling *)shareInstance { static dispatch_once_t onceToken; static TRTCCalling * g_sharedInstance = nil; dispatch_once(&onceToken, ^{ @@ -33,15 +34,15 @@ return g_sharedInstance; } -- (instancetype) init { +- (instancetype)init { self = [super init]; if (self) { self.curLastModel = [[CallModel alloc] init]; self.curLastModel.invitedList = [NSMutableArray array]; - self.curRespList = [NSMutableArray array]; self.curRoomList = [NSMutableArray array]; [self addSignalListener]; self.isHandsFreeOn = YES; + self.isBeingCalled = YES; } return self; } @@ -68,84 +69,104 @@ self.curLastModel.action = CallAction_Call; self.curLastModel.calltype = type; self.curRoomID = [TRTCCallingUtils generateRoomID]; + self.curCallIdDic = [NSMutableDictionary dictionary]; + self.calleeUserIDs = [@[] mutableCopy]; self.curGroupID = groupID; self.curType = type; self.isOnCalling = YES; + self.isBeingCalled = NO; [self enterRoom]; } - // 不在当前邀请列表,新增加的邀请 + + // 如果不在当前邀请列表,则新增 NSMutableArray *newInviteList = [NSMutableArray array]; for (NSString *userID in userIDs) { if (![self.curInvitingList containsObject:userID]) { [newInviteList addObject:userID]; } } + [self.curInvitingList addObjectsFromArray:newInviteList]; + [self.calleeUserIDs addObjectsFromArray:newInviteList]; - // 更新已经回复的列表,移除正在邀请的人 - NSMutableArray *rmRespList = [NSMutableArray array]; - for (NSString *userID in self.curRespList) { - if ([self.curInvitingList containsObject:userID]) { - [rmRespList addObject:userID]; - } + if (!(self.curInvitingList && self.curInvitingList.count > 0)) return; + + //  处理通话邀请 存在GroupID + if ([self checkIsHasGroupIDCall]) { + self.curCallID = [self invite:self.curGroupID action:CallAction_Call model:nil cmdInfo:nil]; + return; } - [self.curRespList removeObjectsInArray:rmRespList]; + // 1v1 使用,多人通话不使用,作用:音视频切换 self.currentCallingUserID = newInviteList.firstObject; - //通话邀请 - if (self.curGroupID.length > 0 && newInviteList.count > 0) { - self.curCallID = [self invite:self.curGroupID action:CallAction_Call model:nil cmdInfo:nil]; - } else { - for (NSString *userID in newInviteList) { - self.curCallID = [self invite:userID action:CallAction_Call model:nil cmdInfo:nil userIds:userIDs]; + for (NSString *userID in self.curInvitingList) { + NSString *callID = [self invite:userID action:CallAction_Call model:nil cmdInfo:nil userIds:userIDs]; + + if (callID && callID.length > 0) { + [self.curCallIdDic setValue:callID forKey:userID]; } } } // 接受当前通话 - (void)accept { + TRTCLog(@"Calling - accept"); [self enterRoom]; - self.currentCallingUserID = self.curGroupID.length > 0 ? self.curGroupID : self.curSponsorForMe; - [self invite:self.curGroupID.length > 0 ? self.curGroupID : self.curSponsorForMe action:CallAction_Accept model:nil cmdInfo:nil]; - [self.curInvitingList removeObject:[TRTCCallingUtils loginUser]]; + self.currentCallingUserID = [self checkIsHasGroupIDCall] ? self.curGroupID : self.curSponsorForMe; } // 拒绝当前通话 - (void)reject { - [self invite:self.curGroupID.length > 0 ? self.curGroupID : self.curSponsorForMe action:CallAction_Reject model:nil cmdInfo:nil]; + TRTCLog(@"Calling - reject"); + [self invite:[self checkIsHasGroupIDCall] ? self.curGroupID : self.curSponsorForMe action:CallAction_Reject model:nil cmdInfo:nil]; self.isOnCalling = NO; self.currentCallingUserID = nil; } // 主动挂断通话 - (void)hangup { + // 如果还没有在通话中,说明还没有接通,所以直接拒绝了 if (!self.isOnCalling) { - return; - } - if (!self.isInRoom) { [self reject]; + TRTCLog(@"Calling - Hangup -> Reject"); return; } - // 没人在通话,取消通话 - // 这个函数供界面主动挂断使用,主动挂断的群通话,不能发 end 事件,end 事件由最后一名成员发出(记录通话时长) - if (self.curRoomList.count == 0 && self.curInvitingList.count > 0) { - if (self.curGroupID.length > 0) { - [self invite:self.curGroupID action:CallAction_Cancel model:nil cmdInfo:nil]; - } else { - [self invite:self.curInvitingList.firstObject action:CallAction_Cancel model:nil cmdInfo:nil]; + + __block BOOL hasCallUser = NO; + [self.curRoomList enumerateObjectsUsingBlock:^(NSString *user, NSUInteger idx, BOOL * _Nonnull stop) { + if ((user && user.length > 0) && ![self.curInvitingList containsObject:user]) { + // 还有正在通话的用户 + hasCallUser = YES; + *stop = YES; } + }]; + + // 非GroupID 组通话处理,主叫需要取消未接通的通话 + if (![self checkIsHasGroupIDCall] || hasCallUser == NO) { + TRTCLog(@"Calling - GroupHangup Send CallAction_Cancel"); + [self.curInvitingList enumerateObjectsUsingBlock:^(NSString *invitedId, NSUInteger idx, BOOL * _Nonnull stop) { + NSString *receiver = [self checkIsHasGroupIDCall] ? @"" : invitedId; + [self invite:receiver action:CallAction_Cancel model:nil cmdInfo:nil]; + }]; } + [self quitRoom]; +} + +- (void)lineBusy { + TRTCLog(@"Calling - lineBusy"); + [self invite:[self checkIsHasGroupIDCall] ? self.curGroupID : self.curSponsorForMe action:CallAction_Linebusy model:nil cmdInfo:nil]; self.isOnCalling = NO; + self.currentCallingUserID = nil; } - (void)switchToAudio { int res = [self checkAudioStatus]; + if (res == 0) { self.switchToAudioCallID = [self invite:self.currentCallingUserID action:CallAction_SwitchToAudio model:nil cmdInfo:nil]; - } - else { + } else { if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { [self.delegate onSwitchToAudio:NO message:@"Local status error"]; } @@ -159,21 +180,22 @@ return 0; } -#pragma mark data +#pragma mark - Data + - (void)setIsOnCalling:(BOOL)isOnCalling { if (isOnCalling && _isOnCalling != isOnCalling) { - //开始通话 - } else if (!isOnCalling && _isOnCalling != isOnCalling) { //退出通话 + // 开始通话 + } else if (!isOnCalling && _isOnCalling != isOnCalling) { // 退出通话 self.curCallID = @""; self.curRoomID = 0; self.curType = CallAction_Unknown; self.curSponsorForMe = @""; - self.isInRoom = NO; self.startCallTS = 0; self.curLastModel = [[CallModel alloc] init]; self.curInvitingList = [NSMutableArray array]; - self.curRespList = [NSMutableArray array]; self.curRoomList = [NSMutableArray array]; + self.curCallIdDic = [NSMutableDictionary dictionary]; + self.calleeUserIDs = [@[] mutableCopy]; } _isOnCalling = isOnCalling; } @@ -223,7 +245,9 @@ } #pragma mark - TRTC Delegate + - (void)enterRoom { + TRTCLog(@"Calling - enterRoom"); TXBeautyManager *beauty = [[TRTCCloud sharedInstance] getBeautyManager]; [beauty setBeautyStyle:TXBeautyStyleNature]; [beauty setBeautyLevel:6]; @@ -243,32 +267,32 @@ [[TRTCCloud sharedInstance] setVideoEncoderParam:videoEncParam]; [[TRTCCloud sharedInstance] setDelegate:self]; - [self setFramework:5]; + [self setFramework]; [[TRTCCloud sharedInstance] enterRoom:param appScene:TRTCAppSceneVideoCall]; [[TRTCCloud sharedInstance] startLocalAudio]; [[TRTCCloud sharedInstance] enableAudioVolumeEvaluation:300]; self.isMicMute = NO; self.isHandsFreeOn = YES; - self.isInRoom = YES; } -- (void)setFramework:(int)framework { +- (void)setFramework { NSDictionary *jsonDic = @{@"api": @"setFramework", - @"params":@{@"framework": @(framework)}}; + @"params":@{@"framework": @(TC_TRTC_FRAMEWORK), + @"component": @(TUICallingConstants.component)}}; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDic options:NSJSONWritingPrettyPrinted error:nil]; NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - TRTCLog(@"jsonString = %@",jsonString); [[TRTCCloud sharedInstance] callExperimentalAPI: jsonString]; } - (void)quitRoom { + TRTCLog(@"Calling - quitRoom"); [[TRTCCloud sharedInstance] stopLocalAudio]; [[TRTCCloud sharedInstance] stopLocalPreview]; [[TRTCCloud sharedInstance] exitRoom]; self.isMicMute = NO; self.isHandsFreeOn = YES; - self.isInRoom = NO; self.currentCallingUserID = nil; + self.isOnCalling = NO; } - (void)startRemoteView:(NSString *)userID view:(UIView *)view { @@ -322,10 +346,24 @@ [self.delegate onCallEnd]; } [self hangup]; + } else { + self.isInRoom = YES; + // 移除当前登录的用户UserId(如果存在) + if ([self.curInvitingList containsObject:[self currentLoginUserId]]) { + [self.curInvitingList removeObject:[self currentLoginUserId]]; + } + + if (self.isBeingCalled) { + [self invite:[self checkIsHasGroupIDCall] ? self.curGroupID : self.curSponsorForMe action:CallAction_Accept model:nil cmdInfo:nil]; + } } } -#pragma mark TRTCCloudDelegate +- (void)onExitRoom:(NSInteger)reason { + self.isInRoom = NO; +} + +#pragma mark - TRTCCloudDelegate - (void)onNetworkQuality:(TRTCQualityInfo *)localQuality remoteQuality:(NSArray *)remoteQuality { if ([self canDelegateRespondMethod:@selector(onNetworkQuality:remoteQuality:)]) { @@ -335,7 +373,11 @@ - (void)onError:(TXLiteAVError)errCode errMsg:(nullable NSString *)errMsg extInfo:(nullable NSDictionary*)extInfo { + TRTCLog(@"Calling - onRemoteUserLeaveRoom errCode: %ld errMsg: %@", errCode, errMsg); self.curLastModel.code = errCode; + if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { + [self.delegate onError:errCode msg:errMsg]; + }; if ([self canDelegateRespondMethod:@selector(onCallEnd)]) { [self.delegate onCallEnd]; } @@ -343,8 +385,9 @@ } - (void)onRemoteUserEnterRoom:(NSString *)userID { + TRTCLog(@"Calling - onRemoteUserEnterRoom userID: %@", userID); // C2C curInvitingList 不要移除 userID,如果是自己邀请的对方,这里移除后,最后发结束信令的时候找不到人 - if ([self.curInvitingList containsObject:userID] && self.curGroupID.length > 0) { + if ([self.curInvitingList containsObject:userID]) { [self.curInvitingList removeObject:userID]; } if (![self.curRoomList containsObject:userID]) { @@ -360,8 +403,12 @@ } - (void)onRemoteUserLeaveRoom:(NSString *)userID reason:(NSInteger)reason { + TRTCLog(@"Calling - onRemoteUserLeaveRoom userID: %@ reason: %d", userID, reason); + // C2C 多人通话添加 - A呼叫B、C, B接通后挂断,C接听,C需要知道B的Accept 和 Reject回调。 + [self sendInviteAction:CallAction_Reject user:userID model:nil]; + // C2C curInvitingList 不要移除 userID,如果是自己邀请的对方,这里移除后,最后发结束信令的时候找不到人 - if ([self.curInvitingList containsObject:userID] && self.curGroupID.length > 0) { + if ([self.curInvitingList containsObject:userID]) { [self.curInvitingList removeObject:userID]; } if ([self.curRoomList containsObject:userID]) { @@ -370,16 +417,18 @@ if ([self canDelegateRespondMethod:@selector(onUserLeave:)]) { [self.delegate onUserLeave:userID]; } - [self checkAutoHangUp]; + [self preExitRoom:userID]; } - (void)onUserAudioAvailable:(NSString *)userID available:(BOOL)available { + TRTCLog(@"Calling - onUserAudioAvailable userID: %@ available: %d", userID, available); if ([self canDelegateRespondMethod:@selector(onUserAudioAvailable:available:)]) { [self.delegate onUserAudioAvailable:userID available:available]; } } - (void)onUserVideoAvailable:(NSString *)userID available:(BOOL)available { + TRTCLog(@"Calling - onUserVideoAvailable userID: %@ available: %d", userID, available); if ([self canDelegateRespondMethod:@selector(onUserVideoAvailable:available:)]) { [self.delegate onUserVideoAvailable:userID available:available]; } @@ -397,7 +446,17 @@ } } -#pragma mark - private method +#pragma mark - Private Method + +// 检查是否为存在GroupID的群通话 +- (BOOL)checkIsHasGroupIDCall { + return (self.curGroupID.length > 0); +} + +- (NSString *)currentLoginUserId { + return [[V2TIMManager sharedInstance] getLoginUser]; +} + - (BOOL)canDelegateRespondMethod:(SEL)selector { return self.delegate && [self.delegate respondsToSelector:selector]; } diff --git a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCallingModel.h b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCallingModel.h similarity index 96% rename from iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCallingModel.h rename to iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCallingModel.h index 5e03c86f2..945dbfad2 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCallingModel.h +++ b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCallingModel.h @@ -58,6 +58,7 @@ typedef NS_ENUM(NSInteger, CallAction) { @interface CallUserModel : TRTCCallingUserModel @property(nonatomic, assign) BOOL isEnter; // 是否进房 +@property(nonatomic, assign) BOOL isAudioAvaliable; // 音频是否可用 @property(nonatomic, assign) BOOL isVideoAvaliable; // 视频是否可用 @property(nonatomic, assign) float volume; // 声音大小 diff --git a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCallingModel.m b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCallingModel.m similarity index 89% rename from iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCallingModel.m rename to iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCallingModel.m index 8e436e465..622655111 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCallingModel.m +++ b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCCallingModel.m @@ -6,6 +6,7 @@ // #import "TRTCCallingModel.h" +#import "TUICallingConstants.h" int SIGNALING_EXTRA_KEY_TIME_OUT = 30; @@ -27,7 +28,6 @@ int SIGNALING_EXTRA_KEY_TIME_OUT = 30; @end - @implementation TRTCCallingUserModel - (id)copyWithZone:(NSZone *)zone { @@ -38,8 +38,11 @@ int SIGNALING_EXTRA_KEY_TIME_OUT = 30; return model; } -@end +- (NSString *)avatar { + return _avatar ?: DEFAULT_AVATETR; +} +@end @implementation CallUserModel @@ -50,6 +53,7 @@ int SIGNALING_EXTRA_KEY_TIME_OUT = 30; model.avatar = self.avatar; model.isEnter = self.isEnter; model.isVideoAvaliable = self.isVideoAvaliable; + model.isAudioAvaliable = self.isAudioAvaliable; model.volume = self.volume; return model; } diff --git a/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCSignalFactory.h b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCSignalFactory.h new file mode 100644 index 000000000..afb6a8a94 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCSignalFactory.h @@ -0,0 +1,38 @@ +// +// TRTCSignalFactory.h +// TUICalling +// +// Created by adams on 2021/6/17. +// + +#import +#import "TRTCCallingUtils.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface TRTCSignalFactory : NSObject + ++ (NSMutableDictionary *)packagingSignalingWithExtInfo:(NSString *)extInfo + roomID:(NSUInteger)roomID + cmd:(NSString *)cmd + cmdInfo:(NSString *)cmdInfo + message:(NSString *)message + callType:(CallType)callType; + ++ (NSMutableDictionary *)packagingSignalingWithExtInfo:(NSString *)extInfo + roomID:(NSUInteger)roomID + cmd:(NSString *)cmd + cmdInfo:(NSString *)cmdInfo + userIds:(NSArray *)userIds + message:(NSString *)message + callType:(CallType)callType; + ++ (NSDictionary *)getDataDictionary: (NSDictionary *)signaling; + ++ (CallType)convertCmdToCallType:(NSString *)cmd; + ++ (NSDictionary *)convertOldSignalingToNewSignaling:(NSDictionary *)oldSignalingDictionary; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCSignalFactory.m b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCSignalFactory.m similarity index 52% rename from iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCSignalFactory.m rename to iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCSignalFactory.m index ea65767f1..0538c9ca5 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCSignalFactory.m +++ b/iOS/TUIKit/TUICalling/Source/Model/Impl/TRTCSignalFactory.m @@ -7,55 +7,38 @@ #import "TRTCSignalFactory.h" #import "TRTCCallingHeader.h" - -NSString *const SIGNALING_EXTRA_KEY_VERSION = @"version"; // 协议版本信息 -NSString *const SIGNALING_EXTRA_KEY_BUSINESSID = @"businessID"; // 业务场景,可以是calling,Karaoke等 -NSString *const SIGNALING_EXTRA_KEY_PLATFORM = @"platform"; // 平台,iOS,Android,Web,Windows,flutter -NSString *const SIGNALING_EXTRA_KEY_EXTINFO = @"extInfo"; // 扩展字段,备用 -NSString *const SIGNALING_EXTRA_KEY_DATA = @"data"; // 携带的指令信息字段 -NSString *const SIGNALING_EXTRA_KEY_ROOMID = @"room_id"; // 房间号 -NSString *const SIGNALING_EXTRA_KEY_CMD = @"cmd"; // 指令 -NSString *const SIGNALING_EXTRA_KEY_CMDINFO = @"cmdInfo"; // 指令信息 (可选) -NSString *const SIGNALING_EXTRA_KEY_MESSAGE = @"message"; // 携带的提示信息,用于UI显示(可选) -NSString *const SIGNALING_EXTRA_KEY_USERIDS = @"userIDs"; // 携带的所有多人通话成员IDs - -// 兼容ver4老字段 -NSString *const SIGNALING_EXTRA_KEY_CALL_TYPE = @"call_type"; -NSString *const SIGNALING_EXTRA_KEY_ROOM_ID = @"room_id"; -NSString *const SIGNALING_EXTRA_KEY_LINE_BUSY = @"line_busy"; -NSString *const SIGNALING_EXTRA_KEY_CALL_END = @"call_end"; -NSString *const SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL = @"switch_to_audio_call"; +#import "TUICallingConstants.h" static const NSUInteger SIGNALING_VERSION = 4; // 信令版本号 -NSString *const SIGNALING_BUSINESSID = @"av_call"; // 业务场景固定Calling -NSString *const SIGNALING_PLATFORM = @"iOS"; // 平台固定iOS - -NSString *const SIGNALING_CMD_VIDEOCALL = @"videoCall"; // 视频通话 -NSString *const SIGNALING_CMD_AUDIOCALL = @"audioCall"; // 语音通话 -NSString *const SIGNALING_CMD_HANGUP = @"hangup"; // 挂断电话 -NSString *const SIGNALING_CMD_SWITCHTOVOICECALL = @"switchToAudio"; // 视频通话切换为语音通话 - -NSString *const SIGNALING_MESSAGE_LINEBUSY = @"lineBusy"; // 拒绝忙线 - - @implementation TRTCSignalFactory -+ (NSMutableDictionary *)packagingSignalingWithExtInfo:(NSString *)extInfo roomID:(NSUInteger)roomID cmd:(NSString *)cmd cmdInfo:(NSString *)cmdInfo message:(NSString *)message callType:(CallType)callType { ++ (NSMutableDictionary *)packagingSignalingWithExtInfo:(NSString *)extInfo + roomID:(NSUInteger)roomID + cmd:(NSString *)cmd + cmdInfo:(NSString *)cmdInfo + message:(NSString *)message + callType:(CallType)callType { return [[self class] packagingSignalingWithExtInfo:extInfo roomID:roomID cmd:cmd cmdInfo:cmdInfo userIds:@[] message:message callType:callType]; } -+ (NSMutableDictionary *)packagingSignalingWithExtInfo:(NSString *)extInfo roomID:(NSUInteger)roomID cmd:(NSString *)cmd cmdInfo:(NSString *)cmdInfo userIds:(NSArray *)userIds message:(NSString *)message callType:(CallType)callType { - NSMutableDictionary *signalingDictionary = [NSMutableDictionary dictionaryWithDictionary:@{SIGNALING_EXTRA_KEY_VERSION:@(SIGNALING_VERSION), - SIGNALING_EXTRA_KEY_BUSINESSID:SIGNALING_BUSINESSID, - SIGNALING_EXTRA_KEY_PLATFORM:SIGNALING_PLATFORM, - SIGNALING_EXTRA_KEY_EXTINFO:extInfo, - SIGNALING_EXTRA_KEY_CALL_TYPE:@(callType), - SIGNALING_EXTRA_KEY_DATA:@{SIGNALING_EXTRA_KEY_ROOMID:@(roomID), - SIGNALING_EXTRA_KEY_CMD:cmd, - SIGNALING_EXTRA_KEY_USERIDS:userIds, - SIGNALING_EXTRA_KEY_CMDINFO:cmdInfo, - SIGNALING_EXTRA_KEY_MESSAGE:message}}]; ++ (NSMutableDictionary *)packagingSignalingWithExtInfo:(NSString *)extInfo + roomID:(NSUInteger)roomID + cmd:(NSString *)cmd + cmdInfo:(NSString *)cmdInfo + userIds:(NSArray *)userIds + message:(NSString *)message + callType:(CallType)callType { + NSMutableDictionary *signalingDictionary = [@{SIGNALING_EXTRA_KEY_VERSION:@(SIGNALING_VERSION), + SIGNALING_EXTRA_KEY_BUSINESSID:SIGNALING_BUSINESSID, + SIGNALING_EXTRA_KEY_PLATFORM:SIGNALING_PLATFORM, + SIGNALING_EXTRA_KEY_EXTINFO:extInfo, + SIGNALING_EXTRA_KEY_CALL_TYPE:@(callType), + SIGNALING_EXTRA_KEY_DATA:@{SIGNALING_EXTRA_KEY_ROOMID:@(roomID), + SIGNALING_EXTRA_KEY_CMD:cmd, + SIGNALING_EXTRA_KEY_USERIDS:userIds, + SIGNALING_EXTRA_KEY_CMDINFO:cmdInfo, + SIGNALING_EXTRA_KEY_MESSAGE:message}} mutableCopy]; return signalingDictionary; } diff --git a/iOS/TUIKit/TUICalling/iOS/Source/model/TRTCCalling.h b/iOS/TUIKit/TUICalling/Source/Model/TRTCCalling.h similarity index 81% rename from iOS/TUIKit/TUICalling/iOS/Source/model/TRTCCalling.h rename to iOS/TUIKit/TUICalling/Source/Model/TRTCCalling.h index 0191b7555..7b62fbcf9 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/model/TRTCCalling.h +++ b/iOS/TUIKit/TUICalling/Source/Model/TRTCCalling.h @@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN typedef void(^CallingActionCallback)(void); typedef void(^ErrorCallback)(int code, NSString *des); -@interface TRTCCalling : NSObject +@interface TRTCCalling : NSObject /// 单例对象 + (TRTCCalling *)shareInstance; @@ -51,32 +51,36 @@ NS_SWIFT_NAME(groupCall(userIDs:type:groupID:)); /// 主动挂断通话 - (void)hangup; +/// 主动操作 - 忙线通话(发送忙线信令到主叫者) +- (void)lineBusy; + +/// 切换到语音通话 - (void)switchToAudio; - (int)checkAudioStatus; -///开启远程用户视频渲染 +/// 开启远程用户视频渲染 - (void)startRemoteView:(NSString *)userId view:(UIView *)view NS_SWIFT_NAME(startRemoteView(userId:view:)); -///关闭远程用户视频渲染 +/// 关闭远程用户视频渲染 - (void)stopRemoteView:(NSString *)userId NS_SWIFT_NAME(stopRemoteView(userId:)); -///打开摄像头 +/// 打开摄像头 - (void)openCamera:(BOOL)frontCamera view:(UIView *)view NS_SWIFT_NAME(openCamera(frontCamera:view:)); -///关闭摄像头 +/// 关闭摄像头 - (void)closeCamara; -///切换摄像头 +/// 切换摄像头 - (void)switchCamera:(BOOL)frontCamera; -///静音操作 +/// 静音操作 - (void)setMicMute:(BOOL)isMute; -///免提操作 +/// 免提操作 - (void)setHandsFree:(BOOL)isHandsFree; @end diff --git a/iOS/TUIKit/TUICalling/iOS/Source/model/TRTCCallingDelegate.h b/iOS/TUIKit/TUICalling/Source/Model/TRTCCallingDelegate.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/model/TRTCCallingDelegate.h rename to iOS/TUIKit/TUICalling/Source/Model/TRTCCallingDelegate.h diff --git a/iOS/TUIKit/TUICalling/Source/Model/Util/TRTCCallingHeader.h b/iOS/TUIKit/TUICalling/Source/Model/Util/TRTCCallingHeader.h new file mode 100644 index 000000000..775645961 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/Model/Util/TRTCCallingHeader.h @@ -0,0 +1,65 @@ +// +// TRTCCallingHeader.h +// TXLiteAVDemo +// +// Created by abyyxwang on 2020/8/4. +// Copyright © 2020 Tencent. All rights reserved. +// + + +#ifndef TRTCCallingHeader_h +#define TRTCCallingHeader_h + +#import "TUICommonUtil.h" + +#define Version 4 //TUIkit 业务版本 +#define APNs_Version 1 //推送版本 +#define APNs_Business_NormalMsg 1 //普通消息推送 +#define APNs_Business_Call 2 //音视频通话推送 + +#define TRTCLog(fmt, ...) TRTCCloudCallingAPILog(fmt, ##__VA_ARGS__) + +/** + Synthsize a weak or strong reference. + + Example: + @weakify(self) + [self doSomething^{ + @strongify(self) + if (!self) return; + ... + }]; + */ +#ifndef weakify +#if DEBUG +#if __has_feature(objc_arc) +#define weakify(object) autoreleasepool{} __weak __typeof__(object) weak##_##object = object; +#else +#define weakify(object) autoreleasepool{} __block __typeof__(object) block##_##object = object; +#endif +#else +#if __has_feature(objc_arc) +#define weakify(object) try{} @finally{} {} __weak __typeof__(object) weak##_##object = object; +#else +#define weakify(object) try{} @finally{} {} __block __typeof__(object) block##_##object = object; +#endif +#endif +#endif + +#ifndef strongify +#if DEBUG +#if __has_feature(objc_arc) +#define strongify(object) autoreleasepool{} __typeof__(object) object = weak##_##object; +#else +#define strongify(object) autoreleasepool{} __typeof__(object) object = block##_##object; +#endif +#else +#if __has_feature(objc_arc) +#define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object; +#else +#define strongify(object) try{} @finally{} __typeof__(object) object = block##_##object; +#endif +#endif +#endif + +#endif /* TRTCCallingHeader_h */ diff --git a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCallingUtils.h b/iOS/TUIKit/TUICalling/Source/Model/Util/TRTCCallingUtils.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCallingUtils.h rename to iOS/TUIKit/TUICalling/Source/Model/Util/TRTCCallingUtils.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCallingUtils.m b/iOS/TUIKit/TUICalling/Source/Model/Util/TRTCCallingUtils.m similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCallingUtils.m rename to iOS/TUIKit/TUICalling/Source/Model/Util/TRTCCallingUtils.m diff --git a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCSignalFactory.h b/iOS/TUIKit/TUICalling/Source/Model/Util/TUICallingConstants.h similarity index 55% rename from iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCSignalFactory.h rename to iOS/TUIKit/TUICalling/Source/Model/Util/TUICallingConstants.h index 6e0e63acf..cd90f5e4e 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCSignalFactory.h +++ b/iOS/TUIKit/TUICalling/Source/Model/Util/TUICallingConstants.h @@ -1,15 +1,20 @@ // -// TRTCSignalFactory.h +// TUICallingConstants.h // TUICalling // -// Created by adams on 2021/6/17. +// Created by noah on 2021/12/28. // #import -#import "TRTCCallingUtils.h" NS_ASSUME_NONNULL_BEGIN +static const int TC_TUICALLING_COMPONENT = 3; +static const int TC_TIMCALLING_COMPONENT = 10; +static const int TC_TRTC_FRAMEWORK = 1; + +@interface TUICallingConstants : NSObject + extern NSString *const SIGNALING_EXTRA_KEY_VERSION; extern NSString *const SIGNALING_EXTRA_KEY_BUSINESSID; extern NSString *const SIGNALING_EXTRA_KEY_PLATFORM; @@ -34,18 +39,27 @@ extern NSString *const SIGNALING_CMD_HANGUP; extern NSString *const SIGNALING_CMD_SWITCHTOVOICECALL; extern NSString *const SIGNALING_MESSAGE_LINEBUSY; extern NSString *const SIGNALING_BUSINESSID; +extern NSString *const SIGNALING_PLATFORM; -@interface TRTCSignalFactory : NSObject +extern NSString *const SIGNALING_CUSTOM_CALL_ACTION; +extern NSString *const SIGNALING_CUSTOM_CALLID; +extern NSString *const SIGNALING_CUSTOM_USER; -+ (NSMutableDictionary *)packagingSignalingWithExtInfo:(NSString *)extInfo roomID:(NSUInteger)roomID cmd:(NSString *)cmd cmdInfo:(NSString *)cmdInfo message:(NSString *)message callType:(CallType)callType; +// onCallEvent常用类型定义 +extern NSString *const EVENT_CALL_HANG_UP; +extern NSString *const EVENT_CALL_LINE_BUSY; +extern NSString *const EVENT_CALL_LINE_BUSY; +extern NSString *const EVENT_CALL_CNACEL; +extern NSString *const EVENT_CALL_TIMEOUT; +extern NSString *const EVENT_CALL_NO_RESP; +extern NSString *const EVENT_CALL_SUCCEED; +extern NSString *const EVENT_CALL_START; +extern NSString *const EVENT_CALL_DECLINE; +extern NSString *const DEFAULT_AVATETR; -+ (NSMutableDictionary *)packagingSignalingWithExtInfo:(NSString *)extInfo roomID:(NSUInteger)roomID cmd:(NSString *)cmd cmdInfo:(NSString *)cmdInfo userIds:(NSArray *)userIds message:(NSString *)message callType:(CallType)callType; +extern NSString *const CALLING_BELL_KEY; -+ (NSDictionary *)getDataDictionary: (NSDictionary *)signaling; - -+ (CallType)convertCmdToCallType:(NSString *)cmd; - -+ (NSDictionary *)convertOldSignalingToNewSignaling:(NSDictionary *)oldSignalingDictionary; +@property (class, nonatomic, assign) int component; @end diff --git a/iOS/TUIKit/TUICalling/Source/Model/Util/TUICallingConstants.m b/iOS/TUIKit/TUICalling/Source/Model/Util/TUICallingConstants.m new file mode 100644 index 000000000..0a5a5ab1e --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/Model/Util/TUICallingConstants.m @@ -0,0 +1,69 @@ +// +// TUICallingConstants.m +// TUICalling +// +// Created by noah on 2021/12/28. +// + +#import "TUICallingConstants.h" + +static int _component = TC_TUICALLING_COMPONENT; + +@implementation TUICallingConstants + +NSString *const SIGNALING_EXTRA_KEY_VERSION = @"version"; // 协议版本信息 +NSString *const SIGNALING_EXTRA_KEY_BUSINESSID = @"businessID"; // 业务场景,可以是calling,Karaoke等 +NSString *const SIGNALING_EXTRA_KEY_PLATFORM = @"platform"; // 平台,iOS,Android,Web,Windows,flutter +NSString *const SIGNALING_EXTRA_KEY_EXTINFO = @"extInfo"; // 扩展字段,备用 +NSString *const SIGNALING_EXTRA_KEY_DATA = @"data"; // 携带的指令信息字段 +NSString *const SIGNALING_EXTRA_KEY_ROOMID = @"room_id"; // 房间号 +NSString *const SIGNALING_EXTRA_KEY_CMD = @"cmd"; // 指令 +NSString *const SIGNALING_EXTRA_KEY_CMDINFO = @"cmdInfo"; // 指令信息 (可选) +NSString *const SIGNALING_EXTRA_KEY_MESSAGE = @"message"; // 携带的提示信息,用于UI显示(可选) +NSString *const SIGNALING_EXTRA_KEY_USERIDS = @"userIDs"; // 携带的所有多人通话成员IDs + +// 兼容ver4老字段 +NSString *const SIGNALING_EXTRA_KEY_CALL_TYPE = @"call_type"; +NSString *const SIGNALING_EXTRA_KEY_ROOM_ID = @"room_id"; +NSString *const SIGNALING_EXTRA_KEY_LINE_BUSY = @"line_busy"; +NSString *const SIGNALING_EXTRA_KEY_CALL_END = @"call_end"; +NSString *const SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL = @"switch_to_audio_call"; + +NSString *const SIGNALING_BUSINESSID = @"av_call"; // 业务场景固定Calling +NSString *const SIGNALING_PLATFORM = @"iOS"; // 平台固定iOS + +NSString *const SIGNALING_CMD_VIDEOCALL = @"videoCall"; // 视频通话 +NSString *const SIGNALING_CMD_AUDIOCALL = @"audioCall"; // 语音通话 +NSString *const SIGNALING_CMD_HANGUP = @"hangup"; // 挂断电话 +NSString *const SIGNALING_CMD_SWITCHTOVOICECALL = @"switchToAudio"; // 视频通话切换为语音通话 + +NSString *const SIGNALING_MESSAGE_LINEBUSY = @"lineBusy"; // 拒绝忙线 + +NSString *const SIGNALING_CUSTOM_CALL_ACTION = @"call_action"; +NSString *const SIGNALING_CUSTOM_CALLID = @"callid"; +NSString *const SIGNALING_CUSTOM_USER = @"user"; + +// onCallEvent常用类型定义 +NSString *const EVENT_CALL_HANG_UP = @"Hangup"; +NSString *const EVENT_CALL_LINE_BUSY = @"LineBusy"; +NSString *const EVENT_CALL_CNACEL = @"Cancel"; +NSString *const EVENT_CALL_TIMEOUT = @"Timeout"; +NSString *const EVENT_CALL_NO_RESP = @"NoResp"; +NSString *const EVENT_CALL_SUCCEED = @"Succeed"; +NSString *const EVENT_CALL_START = @"Start"; +NSString *const EVENT_CALL_DECLINE = @"Decline"; + +NSString *const CALLING_BELL_KEY = @"CallingBell"; + +// 如果头像为空的默认头像 +NSString *const DEFAULT_AVATETR = @"https://imgcache.qq.com/qcloud/public/static//avatar1_100.20191230.png"; + ++ (int)component { + return _component; +} + ++ (void)setComponent:(int)component { + _component = component; +} + +@end diff --git a/iOS/TUIKit/TUICalling/iOS/Source/Service/TUICallingService.h b/iOS/TUIKit/TUICalling/Source/Service/TUICallingService.h similarity index 99% rename from iOS/TUIKit/TUICalling/iOS/Source/Service/TUICallingService.h rename to iOS/TUIKit/TUICalling/Source/Service/TUICallingService.h index 3155937de..8b334e932 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/Service/TUICallingService.h +++ b/iOS/TUIKit/TUICalling/Source/Service/TUICallingService.h @@ -8,7 +8,6 @@ #import NS_ASSUME_NONNULL_BEGIN - @interface TUICallingService : NSObject @end diff --git a/iOS/TUIKit/TUICalling/iOS/Source/Service/TUICallingService.m b/iOS/TUIKit/TUICalling/Source/Service/TUICallingService.m similarity index 84% rename from iOS/TUIKit/TUICalling/iOS/Source/Service/TUICallingService.m rename to iOS/TUIKit/TUICalling/Source/Service/TUICallingService.m index fb3cee299..a5f9f926c 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/Service/TUICallingService.m +++ b/iOS/TUIKit/TUICalling/Source/Service/TUICallingService.m @@ -8,11 +8,12 @@ #import "TUICallingService.h" #import #import -#import "TUICallingManager.h" +#import "TUICalling.h" #import "TUICommonUtil.h" #import "TUIGlobalization.h" #import "NSDictionary+TUISafe.h" #import "TRTCCalling+Signal.h" +#import "TUICallingConstants.h" @interface TUICallingService () @property(nonatomic, strong) NSMutableArray *extentions; @@ -38,18 +39,18 @@ - (instancetype)init { self = [super init]; if (self) { - [TUICallingManager shareInstance]; + [TUICalling shareInstance]; self.extentions = [NSMutableArray array]; } return self; } - (void)startCall:(NSString *)groupID userIDs:(NSArray *)userIDs callingType:(TUICallingType)callingType { - if ([[TUICallingManager shareInstance] respondsToSelector:@selector(setGroupID:onlineUserOnly:)]) { - [[TUICallingManager shareInstance] performSelector:@selector(setGroupID:onlineUserOnly:) withObject:groupID withObject:false]; + if ([[TUICalling shareInstance] respondsToSelector:@selector(setGroupID:onlineUserOnly:)]) { + [[TUICalling shareInstance] performSelector:@selector(setGroupID:onlineUserOnly:) withObject:groupID withObject:false]; } - [[TUICallingManager shareInstance] call:userIDs type:callingType]; + [[TUICalling shareInstance] call:userIDs type:callingType]; } #pragma mark - TUIServiceProtocol @@ -58,7 +59,10 @@ if (![TUICommonUtil checkDictionaryValid:param]) { return nil; } - + TUICallingConstants.component = TC_TIMCALLING_COMPONENT; + if (param && [param tui_objectForKey:@"component" asClass:NSNumber.class]) { + TUICallingConstants.component = [[param tui_objectForKey:@"component" asClass:NSNumber.class] intValue]; + } if ([method isEqualToString:TUICore_TUICallingService_ShowCallingViewMethod]) { NSArray *userIDs = [param tui_objectForKey:TUICore_TUICallingService_ShowCallingViewMethod_UserIDsKey asClass:NSArray.class]; TUICallingType callingType = (TUICallingType)[[param tui_objectForKey:TUICore_TUICallingService_ShowCallingViewMethod_CallTypeKey asClass:NSString.class] integerValue]; @@ -69,8 +73,8 @@ V2TIMSignalingInfo *signalingInfo = [param tui_objectForKey:TUICore_TUICallingService_ShowCallingViewMethod_SignalingInfo asClass:V2TIMSignalingInfo.class]; NSString *groupID = signalingInfo.groupID; - if ([[TUICallingManager shareInstance] respondsToSelector:@selector(setGroupID:)]) { - [[TUICallingManager shareInstance] performSelector:@selector(setGroupID:) withObject:groupID]; + if ([[TUICalling shareInstance] respondsToSelector:@selector(setGroupID:)]) { + [[TUICalling shareInstance] performSelector:@selector(setGroupID:) withObject:groupID]; } [[TRTCCalling shareInstance] onReceiveGroupCallAPNs:signalingInfo]; @@ -84,6 +88,10 @@ if (!key || ![TUICommonUtil checkDictionaryValid:param]) { return nil; } + TUICallingConstants.component = TC_TIMCALLING_COMPONENT; + if (param && [param tui_objectForKey:@"component" asClass:NSNumber.class]) { + TUICallingConstants.component = [[param tui_objectForKey:@"component" asClass:NSNumber.class] intValue]; + } NSString *call_groupID = [param tui_objectForKey:TUICore_TUIChatExtension_GetMoreCellInfo_GroupID asClass:NSString.class]; NSString *call_userID = [param tui_objectForKey:TUICore_TUIChatExtension_GetMoreCellInfo_UserID asClass:NSString.class]; if (call_groupID.length == 0 && call_userID.length == 0) { @@ -100,7 +108,7 @@ [view addSubview:imageView]; UILabel *titleLabel = [[UILabel alloc] init]; - [titleLabel setFont:[UIFont systemFontOfSize:14]]; + [titleLabel setFont:[UIFont systemFontOfSize:10]]; [titleLabel setTextColor:[UIColor grayColor]]; titleLabel.textAlignment = NSTextAlignmentCenter; titleLabel.frame = CGRectMake(0, imageView.frame.origin.y + imageView.frame.size.height, imageView.frame.size.width + 10, TMoreCell_Title_Height); diff --git a/iOS/TUIKit/TUICalling/iOS/Source/Service/TUICallingManager.h b/iOS/TUIKit/TUICalling/Source/TUICalling.h similarity index 80% rename from iOS/TUIKit/TUICalling/iOS/Source/Service/TUICallingManager.h rename to iOS/TUIKit/TUICalling/Source/TUICalling.h index 46abf9c3a..7ee294409 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/Service/TUICallingManager.h +++ b/iOS/TUIKit/TUICalling/Source/TUICalling.h @@ -1,11 +1,12 @@ // -// TUICallingManager.h +// TUICalling.h // TUICalling // // Created by noah on 2021/8/28. // #import +#import typedef NS_ENUM(NSUInteger, TUICallingType) { TUICallingTypeAudio, // 语音呼叫 @@ -28,6 +29,8 @@ typedef NS_ENUM(NSUInteger, TUICallingEvent) { TUICallingEventCallFailed = 400, }; +typedef void (^TUICallingCallback)(int code, NSString * _Nonnull desc); + NS_ASSUME_NONNULL_BEGIN @protocol TUICallingListerner @@ -61,7 +64,7 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface TUICallingManager : NSObject +@interface TUICalling : NSObject + (instancetype)shareInstance; @@ -79,6 +82,17 @@ NS_ASSUME_NONNULL_BEGIN /// @param filePath 音频文件路径 - (void)setCallingBell:(NSString *)filePath NS_SWIFT_NAME(setCallingBell(filePath:)); +/// 设置用户昵称 +/// @param nickname 用户昵称 (长度不得超过500个字节) +/// @param callback 结果回调 code = 0 代表成功,否则为错误码 +- (void)setUserNickname:(NSString *)nickname callback:(TUICallingCallback)callback NS_SWIFT_NAME(setUserNickname(nickname:callback:)); + +/// 设置用户头像 +/// @param avatar 用户头像, 头像必须是URL格式 (长度不得超过500个字节) +/// 例如: https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar1.png +/// @param callback 结果回调 code = 0 代表成功,否则为错误码 +- (void)setUserAvatar:(NSString *)avatar callback:(TUICallingCallback)callback NS_SWIFT_NAME(setUserAvatar(avatar:callback:)); + /// 开启静音模式(默认关) - (void)enableMuteMode:(BOOL)enable NS_SWIFT_NAME(enableMuteMode(enable:)); diff --git a/iOS/TUIKit/TUICalling/iOS/Source/Service/TUICallingManager.m b/iOS/TUIKit/TUICalling/Source/TUICalling.m similarity index 58% rename from iOS/TUIKit/TUICalling/iOS/Source/Service/TUICallingManager.m rename to iOS/TUIKit/TUICalling/Source/TUICalling.m index a99749fff..97254a3e7 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/Service/TUICallingManager.m +++ b/iOS/TUIKit/TUICalling/Source/TUICalling.m @@ -1,16 +1,14 @@ // -// TUICallingManager.m +// TUICalling.m // TUICalling // // Created by noah on 2021/8/28. // -#import "TUICallingManager.h" +#import "TUICalling.h" #import "TRTCCallingUtils.h" #import "TRTCCallingDelegate.h" #import "TRTCCalling.h" -#import -#import "TUICommonUtil.h" #import #import "CallingLocalized.h" #import "TRTCCalling+Signal.h" @@ -19,6 +17,10 @@ #import "TUIGroupCallingView.h" #import "TRTCGCDTimer.h" #import "TUICallingAudioPlayer.h" +#import "TUICallingConstants.h" +#import "TRTCCallingHeader.h" +#import +#import typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { TUICallingUserRemoveReasonLeave, @@ -27,53 +29,43 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { TUICallingUserRemoveReasonBusy }; -@interface TUICallingManager () +@interface TUICalling () /// 存储监听者对象 @property (nonatomic, weak) id listener; - ///Calling 主视图 @property (nonatomic, strong) TUICallingBaseView *callingView; - /// 记录当前的呼叫类型 语音、视频 @property (nonatomic, assign) TUICallingType currentCallingType; - /// 记录当前的呼叫用户类型 主动、被动 @property (nonatomic, assign) TUICallingRole currentCallingRole; - -/// 铃声的资源地址 -@property (nonatomic, copy) NSString *bellFilePath; - /// 记录是否开启静音模式 需考虑恢复默认页面 @property (nonatomic, assign) BOOL enableMuteMode; - /// 记录是否开启悬浮窗 @property (nonatomic, assign) BOOL enableFloatWindow; - /// 记录是否自定义视图 @property (nonatomic, assign) BOOL enableCustomViewRoute; - /// 记录IM专属 groupID @property (nonatomic, copy) NSString *groupID; - /// 记录原始userIDs数据(不包括自己) @property (nonatomic, strong) NSArray *userIDs; - /// 记录计时器名称 @property (nonatomic, copy) NSString *timerName; - /// 记录通话时间 单位:秒 @property (nonatomic, assign) NSInteger totalTime; +/// 记录是否需要继续播放来电铃声 +@property (nonatomic, assign) BOOL needContinuePlaying; + @end -@implementation TUICallingManager +@implementation TUICalling + (instancetype)shareInstance { static dispatch_once_t onceToken; - static TUICallingManager * t_sharedInstance = nil; + static TUICalling * t_sharedInstance = nil; dispatch_once(&onceToken, ^{ - t_sharedInstance = [[TUICallingManager alloc] init]; + t_sharedInstance = [[TUICalling alloc] init]; }); return t_sharedInstance; } @@ -84,25 +76,23 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { _enableMuteMode = NO; _enableFloatWindow = NO; _currentCallingRole = NO; + _enableCustomViewRoute = NO; [[TRTCCalling shareInstance] addDelegate:self]; + [self registerNotifications]; } return self; } +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + #pragma mark - Public method - (void)call:(NSArray *)userIDs type:(TUICallingType)type { if (![TUICommonUtil checkArrayValid:userIDs]) return; self.userIDs = [NSArray arrayWithArray:userIDs]; - - if (self.listener && [self.listener respondsToSelector:@selector(shouldShowOnCallView)]) { - if (![self.listener shouldShowOnCallView]) { - [self onLineBusy:@""]; // 仅仅提示用户忙线,不做calling处理 - return; - } - } - self.currentCallingType = type; self.currentCallingRole = TUICallingRoleCall; @@ -119,12 +109,13 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { CallUserModel *model = [self covertUser:infoList.firstObject]; if (userIDs.count >= 2 || self.groupID.length > 0) { - [self initCallingViewWithUser:model isGroup:YES]; + [self initCallingViewIsGroup:YES]; NSMutableArray *ids = [NSMutableArray arrayWithArray:userIDs]; [ids addObject:self.currentUserId]; + [self.callingView setCurrentUser:model]; [self configCallViewWithUserIDs:[ids copy] sponsor:nil]; } else { - [self initCallingViewWithUser:nil isGroup:NO]; + [self initCallingViewIsGroup:NO]; [self configCallViewWithUserIDs:userIDs sponsor:nil]; } @@ -141,9 +132,40 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { } - (void)setCallingBell:(NSString *)filePath { - if (filePath && ![filePath hasPrefix:@"http"]) { - self.bellFilePath = filePath; + if(!(filePath && [filePath isKindOfClass:NSString.class] && filePath.length > 0)) { + return; } + + if ([filePath hasPrefix:@"http"]) { + NSURLSession *session = [NSURLSession sharedSession]; + NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:[NSURL URLWithString:filePath] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) { + if (error != nil) { + TRTCLog(@"SetCallingBell Error: %@", error.localizedDescription); + return; + } + + if (location != nil) { + NSString *oldBellFilePath = [NSUserDefaults.standardUserDefaults objectForKey:CALLING_BELL_KEY]; + [[NSFileManager defaultManager] removeItemAtPath:oldBellFilePath error:nil]; + NSString *filePathStr = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename]; + [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePathStr] error:nil]; + [NSUserDefaults.standardUserDefaults setObject:filePathStr ?: @"" forKey:CALLING_BELL_KEY]; + [NSUserDefaults.standardUserDefaults synchronize]; + } + }]; + [downloadTask resume]; + } else { + [NSUserDefaults.standardUserDefaults setObject:filePath forKey:CALLING_BELL_KEY]; + [NSUserDefaults.standardUserDefaults synchronize]; + } +} + +- (void)setUserNickname:(NSString *)nickname callback:(TUICallingCallback)callback { + [self setUserNickname:nickname avatar:nil callback:callback]; +} + +- (void)setUserAvatar:(NSString *)avatar callback:(TUICallingCallback)callback { + [self setUserNickname:nil avatar:avatar callback:callback]; } - (void)enableMuteMode:(BOOL)enable { @@ -161,6 +183,25 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { #pragma mark - Private method +- (void)registerNotifications { + if (@available(iOS 13.0, *)) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(appWillEnterForeground) + name:UISceneWillEnterForegroundNotification object:nil]; + } else { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(appWillEnterForeground) + name:UIApplicationWillEnterForegroundNotification object:nil]; + } +} + +- (void)appWillEnterForeground { + if (self.needContinuePlaying) { + [self playAudioToCalled]; + } + self.needContinuePlaying = NO; +} + - (void)setGroupID:(NSString *)groupID onlineUserOnly:(NSNumber *)onlineUserOnly { self.groupID = groupID; [TRTCCalling shareInstance].onlineUserOnly = [onlineUserOnly boolValue]; @@ -171,13 +212,11 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { } - (void)callStartWithUserIDs:(NSArray *)userIDs type:(TUICallingType)type role:(TUICallingRole)role { - if (self.enableCustomViewRoute) { - if (self.listener && [self.listener respondsToSelector:@selector(callStart:type:role:viewController:)]) { - UIViewController *callVC = [[UIViewController alloc] init]; - callVC.view.backgroundColor = [UIColor clearColor]; - [callVC.view addSubview:self.callingView]; - [self.listener callStart:userIDs type:type role:role viewController:callVC]; - } + if (self.enableCustomViewRoute && self.listener && [self.listener respondsToSelector:@selector(callStart:type:role:viewController:)]) { + UIViewController *callVC = [[UIViewController alloc] init]; + callVC.view.backgroundColor = [UIColor clearColor]; + [callVC.view addSubview:self.callingView]; + [self.listener callStart:userIDs type:type role:role viewController:callVC]; } else { [self.callingView show]; } @@ -185,64 +224,104 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { if (self.enableMuteMode) { return; } + if (role == TUICallingRoleCall) { playAudio(CallingAudioTypeDial); return; } - if (self.bellFilePath) { - playAudioWithFilePath(self.bellFilePath); - } else { - playAudio(CallingAudioTypeCalled); + + if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { + self.needContinuePlaying = YES; + return; } + + [self playAudioToCalled]; +} + + +- (void)playAudioToCalled { + NSString *bellFilePath = [NSUserDefaults.standardUserDefaults objectForKey:CALLING_BELL_KEY]; + if (bellFilePath && playAudioWithFilePath(bellFilePath)) { + return; + } + playAudio(CallingAudioTypeCalled); +} + +- (void)handleStopAudio { + stopAudio(); + self.needContinuePlaying = NO; } - (void)handleCallEnd { - if (self.enableCustomViewRoute) { - if (self.listener && [self.listener respondsToSelector:@selector(callEnd:type:role:totalTime:)]) { - [self.listener callEnd:self.userIDs type:self.currentCallingType role:self.currentCallingRole totalTime:(CGFloat)self.totalTime]; - } - } else if (self.callingView) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self.callingView disMiss]; - self.callingView = nil; - }); + if (self.enableCustomViewRoute && self.listener && [self.listener respondsToSelector:@selector(callEnd:type:role:totalTime:)]) { + [self.listener callEnd:self.userIDs type:self.currentCallingType role:self.currentCallingRole totalTime:(CGFloat)self.totalTime]; } - stopAudio(); + [self.callingView disMiss]; + self.callingView = nil; + [self handleStopAudio]; [TRTCGCDTimer canelTimer:self.timerName]; + [self enableAutoLockScreen:YES]; self.timerName = nil; self.groupID = nil; } - (void)handleCallEvent:(TUICallingEvent)event message:(NSString *)message { - if (self.enableCustomViewRoute) { - if (self.listener && [self.listener respondsToSelector:@selector(onCallEvent:type:role:message:)]) { - [self.listener onCallEvent:event type:self.currentCallingType role:self.currentCallingRole message:message]; - } + if (self.enableCustomViewRoute && self.listener && [self.listener respondsToSelector:@selector(onCallEvent:type:role:message:)]) { + [self.listener onCallEvent:event type:self.currentCallingType role:self.currentCallingRole message:message]; } } +- (void)enableAutoLockScreen:(BOOL)isEnable { + [UIApplication sharedApplication].idleTimerDisabled = !isEnable; +} + +- (void)setUserNickname:(NSString *)nickname avatar:(NSString *)avatar callback:(TUICallingCallback)callback { + [[V2TIMManager sharedInstance] getUsersInfo:@[TUILogin.getUserID ?: @""] succ:^(NSArray *infoList) { + V2TIMUserFullInfo *info = infoList.firstObject; + + if ((nickname && [nickname isKindOfClass:NSString.class] && [nickname isEqualToString:info.nickName]) || + (avatar && [avatar isKindOfClass:NSString.class] && [avatar isEqualToString:info.faceURL])) { + callback(0, @"success"); + return; + } + if ([nickname isKindOfClass:NSString.class] && nickname.length > 0) { + info.nickName = nickname; + } + if ([avatar isKindOfClass:NSString.class] && avatar.length > 0) { + info.faceURL = avatar; + } + + [[V2TIMManager sharedInstance] setSelfInfo:info succ:^{ + callback(0, @"success"); + } fail:^(int code, NSString *desc) { + callback(code, desc); + }]; + } fail:^(int code, NSString *desc) { + callback(code, desc); + }]; +} + #pragma mark - TUIInvitedActionProtocal - (void)acceptCalling { - stopAudio(); + [self handleStopAudio]; [[TRTCCalling shareInstance] accept]; [self.callingView acceptCalling]; - [self handleCallEvent:TUICallingEventCallSucceed message:CallingLocalize(@"Demo.TRTC.Calling.answer")]; - [self handleCallEvent:TUICallingEventCallStart message:CallingLocalize(@"Demo.TRTC.Calling.answer")]; [self startTimer]; + [self enableAutoLockScreen:NO]; } - (void)refuseCalling { [[TRTCCalling shareInstance] reject]; [self.callingView refuseCalling]; - [self handleCallEvent:TUICallingEventCallFailed message:CallingLocalize(@"Demo.TRTC.Calling.decline")]; + [self handleCallEvent:TUICallingEventCallFailed message:EVENT_CALL_DECLINE]; [self handleCallEnd]; } - (void)hangupCalling { [[TRTCCalling shareInstance] hangup]; - [self handleCallEvent:TUICallingEventCallEnd message:CallingLocalize(@"Demo.TRTC.Calling.hangup")]; + [self handleCallEvent:TUICallingEventCallEnd message:EVENT_CALL_HANG_UP]; [self handleCallEnd]; } @@ -250,6 +329,12 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { -(void)onError:(int)code msg:(NSString * _Nullable)msg { NSLog(@"onError: code %d, msg %@", code, msg); + NSString *toast = [NSString stringWithFormat:@"Error code: %d, Message: %@", code, msg]; + if (code == ERR_ROOM_ENTER_FAIL) { + [self makeToast:toast duration:3 position:TUICSToastPositionCenter]; + } else if (code != ERR_INVALID_PARAMETERS) { + [self makeToast:toast]; + } [self handleCallEvent:TUICallingEventCallFailed message:msg]; } @@ -275,8 +360,17 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { isFromGroup:(BOOL)isFromGroup callType:(CallType)callType { NSLog(@"log: onInvited sponsor:%@ userIds:%@", sponsor, userIDs); + if (![TUICommonUtil checkArrayValid:userIDs]) { + return; + } - if (![TUICommonUtil checkArrayValid:userIDs]) return; + if (self.listener && [self.listener respondsToSelector:@selector(shouldShowOnCallView)]) { + if (![self.listener shouldShowOnCallView]) { + [[TRTCCalling shareInstance] lineBusy]; + [self onLineBusy:@""]; // 仅仅提示用户忙线,不做calling处理 + return; + } + } self.userIDs = [NSArray arrayWithArray:userIDs]; self.currentCallingRole = TTUICallingRoleCalled; @@ -284,52 +378,42 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { if ([self checkAuthorizationStatusIsDenied]) return; - NSMutableArray *userIds = [NSMutableArray arrayWithArray:userIDs]; + NSMutableArray *userArray = [NSMutableArray arrayWithArray:userIDs]; if (isFromGroup) { - [userIds addObject:sponsor]; + [userArray addObject:sponsor]; } - if (userIds.count >= 2 || isFromGroup) { + if (userArray.count >= 2 || isFromGroup) { if (!self.currentUserId) return; - __weak typeof(self)weakSelf = self; - NSMutableArray *users = [NSMutableArray arrayWithObject:self.currentUserId]; - if (userIds.count > 0) { - [users addObjectsFromArray:userIds]; - } - [[V2TIMManager sharedInstance] getUsersInfo:users succ:^(NSArray *infoList) { - if (infoList.count == 0) { - return; - } - __strong typeof(weakSelf)self = weakSelf; - __block V2TIMUserFullInfo *currentInfo = nil; - [infoList enumerateObjectsUsingBlock:^(V2TIMUserFullInfo * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - if ([obj.userID isEqualToString:[self currentUserId]]) { - currentInfo = obj; - *stop = YES; - } - }]; - CallUserModel *model = [self covertUser:currentInfo]; - [self initCallingViewWithUser:model isGroup:YES]; - [self callStartWithUserIDs:userIDs type:[self transformCallType:callType] role:TTUICallingRoleCalled]; - [self refreshCallingViewWithUserIDs:userIDs sponsor:sponsor]; + + [userArray addObject:self.currentUserId]; + [self initCallingViewIsGroup:YES]; + [self callStartWithUserIDs:userArray type:[self transformCallType:callType] role:TTUICallingRoleCalled]; + + __weak typeof(self) weakSelf = self; + [[V2TIMManager sharedInstance] getUsersInfo:@[[self currentUserId]] succ:^(NSArray *infoList) { + if (infoList.count == 0) return; + + [weakSelf.callingView setCurrentUser:[weakSelf covertUser:[infoList firstObject]]]; + [weakSelf refreshCallingViewWithUserIDs:userIDs sponsor:sponsor]; } fail:^(int code, NSString *desc) { - NSLog(@"V2TIMManager getUsersInfo: code %d, msg %@", code, desc); + NSLog(@"OnInvited getUsersInfo: code %d, msg %@", code, desc); }]; } else { - [self initCallingViewWithUser:nil isGroup:NO]; - [self callStartWithUserIDs:userIDs type:[self transformCallType:callType] role:TTUICallingRoleCalled]; - [self refreshCallingViewWithUserIDs:userIDs sponsor:sponsor]; + [self initCallingViewIsGroup:NO]; + [self callStartWithUserIDs:[userArray copy] type:[self transformCallType:callType] role:TTUICallingRoleCalled]; + [self refreshCallingViewWithUserIDs:[userArray copy] sponsor:sponsor]; } } -- (void)initCallingViewWithUser:(CallUserModel *)userModel isGroup:(BOOL)isGroup { +- (void)initCallingViewIsGroup:(BOOL)isGroup { TUICallingBaseView *callingView = nil; BOOL isCallee = (self.currentCallingRole == TTUICallingRoleCalled); BOOL isVideo = (self.currentCallingType == TUICallingTypeVideo); if (isGroup) { - callingView = (TUICallingBaseView *)[[TUIGroupCallingView alloc] initWithUser:userModel isVideo:isVideo isCallee:isCallee]; + callingView = (TUICallingBaseView *)[[TUIGroupCallingView alloc] initWithIsVideo:isVideo isCallee:isCallee]; } else { callingView = (TUICallingBaseView *)[[TUICallingView alloc] initWithIsVideo:isVideo isCallee:isCallee]; } @@ -354,15 +438,15 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { - (void)onUserEnter:(NSString *)uid { NSLog(@"log: onUserEnter: %@", uid); - stopAudio(); + [self handleStopAudio]; __weak typeof(self) weakSelf = self; [[V2TIMManager sharedInstance] getUsersInfo:@[uid] succ:^(NSArray *infoList) { V2TIMUserFullInfo *userInfo = [infoList firstObject]; if (!userInfo) return; [weakSelf startTimer]; + [weakSelf enableAutoLockScreen:NO]; CallUserModel *userModel = [weakSelf covertUser:userInfo]; [weakSelf.callingView enterUser:userModel]; - [self.callingView makeToast: [NSString stringWithFormat:@"%@ %@", userModel.name, CallingLocalize(@"Demo.TRTC.calling.callingbegan")] ]; } fail:nil]; } @@ -388,26 +472,34 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { - (void)onCallingCancel:(NSString *)uid { NSLog(@"log: onCallingCancel: %@", uid); - [[TUICommonUtil getRootWindow] makeToast:CallingLocalize(@"Demo.TRTC.calling.callingcancel")]; + [self makeToast:CallingLocalize(@"Demo.TRTC.calling.callingcancel") uid:uid]; [self handleCallEnd]; - [self handleCallEvent:TUICallingEventCallFailed message:CallingLocalize(@"Demo.TRTC.Calling.callingcancel")]; + [self handleCallEvent:TUICallingEventCallFailed message:EVENT_CALL_CNACEL]; } - (void)onCallingTimeOut { NSLog(@"log: onCallingTimeOut"); - [[TUICommonUtil getRootWindow] makeToast:CallingLocalize(@"Demo.TRTC.calling.callingtimeout")]; + [self makeToast:CallingLocalize(@"Demo.TRTC.calling.callingtimeout")]; [self handleCallEnd]; - [self handleCallEvent:TUICallingEventCallFailed message:CallingLocalize(@"Demo.TRTC.Calling.callingtimeout")]; + [self handleCallEvent:TUICallingEventCallFailed message:EVENT_CALL_TIMEOUT]; } - (void)onCallEnd { NSLog(@"onCallEnd \n %s", __FUNCTION__); [self handleCallEnd]; - [self handleCallEvent:TUICallingEventCallEnd message:CallingLocalize(@"Demo.TRTC.Calling.hangup")]; + [self handleCallEvent:TUICallingEventCallEnd message:EVENT_CALL_HANG_UP]; } - (void)onUserAudioAvailable:(NSString *)uid available:(BOOL)available { NSLog(@"log: onUserAudioAvailable: %@, available: %d",uid, available); + if (self.callingView) { + CallUserModel *userModel = [self.callingView getUserById:uid]; + if (userModel) { + userModel.isEnter = YES; + userModel.isAudioAvaliable = available; + [self.callingView updateUser:userModel animated:NO]; + } + } } - (void)onUserVoiceVolume:(NSString *)uid volume:(UInt32)volume { @@ -416,6 +508,7 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { if (user) { CallUserModel *newUser = user; + newUser.isEnter = YES; newUser.volume = (CGFloat)volume / 100; [self.callingView updateUserVolume:newUser]; } @@ -451,30 +544,31 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { CallUserModel *callUserModel = [weakSelf covertUser:userInfo]; [weakSelf.callingView leaveUser:callUserModel]; - NSString *toast = callUserModel.name ?: @""; + NSString *toast = @""; switch (removeReason) { - case TUICallingUserRemoveReasonLeave: - toast = [NSString stringWithFormat:@"%@%@", toast, CallingLocalize(@"Demo.TRTC.calling.callingleave")]; - break; case TUICallingUserRemoveReasonReject: - toast = [NSString stringWithFormat:@"%@%@", toast, CallingLocalize(@"Demo.TRTC.calling.callingrefuse")]; - [self handleCallEvent:TUICallingEventCallFailed message:toast]; + if (![TRTCCalling shareInstance].isBeingCalled) { + toast = CallingLocalize(@"Demo.TRTC.calling.callingrefuse"); + } + [weakSelf handleCallEvent:TUICallingEventCallFailed message:EVENT_CALL_HANG_UP]; break; case TUICallingUserRemoveReasonNoresp: - toast = [NSString stringWithFormat:@"%@%@", toast, CallingLocalize(@"Demo.TRTC.calling.callingnoresponse")]; - [self handleCallEvent:TUICallingEventCallFailed message:toast]; + toast = CallingLocalize(@"Demo.TRTC.calling.callingnoresponse"); + [weakSelf handleCallEvent:TUICallingEventCallFailed message:EVENT_CALL_NO_RESP]; break; case TUICallingUserRemoveReasonBusy: - toast = [NSString stringWithFormat:@"%@%@", toast, CallingLocalize(@"Demo.TRTC.calling.callingbusy")]; - [self handleCallEvent:TUICallingEventCallFailed message:toast]; + toast = CallingLocalize(@"Demo.TRTC.calling.callingbusy"); + [weakSelf handleCallEvent:TUICallingEventCallFailed message:EVENT_CALL_LINE_BUSY]; break; default: break; } if (toast && toast.length > 0) { - [[TUICommonUtil getRootWindow] makeToast:toast]; + NSString *userStr = callUserModel.name ?: callUserModel.userId; + toast = [NSString stringWithFormat:@"%@ %@", userStr, toast]; + [weakSelf makeToast:toast]; } } fail:nil]; } @@ -495,6 +589,33 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { #pragma mark - Private method +- (void)makeToast:(NSString *)toast uid:(NSString *)uid { + if (uid && uid.length > 0) { + __weak typeof(self) weakSelf = self; + [[V2TIMManager sharedInstance] getUsersInfo:@[uid] succ:^(NSArray *infoList) { + V2TIMUserFullInfo *userFullInfo = [infoList firstObject]; + NSString *toastStr = [NSString stringWithFormat:@"%@ %@", userFullInfo.nickName ?: userFullInfo.userID, toast]; + [weakSelf makeToast:toastStr duration:3 position:nil]; + } fail:nil]; + return; + } + [self makeToast:toast duration:3 position:nil]; +} + +- (void)makeToast:(NSString *)toast { + [self makeToast:toast duration:3 position:nil]; +} + +- (void)makeToast:(NSString *)toast duration:(NSTimeInterval)duration position:(id)position { + if (!toast || toast.length <= 0) return; + + if (self.callingView) { + [self.callingView makeToast:toast duration:duration position:position]; + } else { + [[TUICommonUtil getRootWindow] makeToast:toast duration:duration position:position]; + } +} + - (NSString *)currentUserId { return [[V2TIMManager sharedInstance] getLoginUser]; } @@ -562,9 +683,8 @@ typedef NS_ENUM(NSUInteger, TUICallingUserRemoveReason) { - (void)startTimer { if (self.timerName.length) return; - [self handleCallEvent:TUICallingEventCallSucceed message:CallingLocalize(@"Demo.TRTC.Calling.answer")]; - [self handleCallEvent:TUICallingEventCallStart message:CallingLocalize(@"Demo.TRTC.Calling.answer")]; - + [self handleCallEvent:TUICallingEventCallSucceed message:EVENT_CALL_SUCCEED]; + [self handleCallEvent:TUICallingEventCallStart message:EVENT_CALL_START]; self.totalTime = 0; NSTimeInterval interval = 1.0; __weak typeof(self) weakSelf = self; diff --git a/iOS/TUIKit/TUICalling/Source/TUICallingKit_Enterprise/TUICallingKit.h b/iOS/TUIKit/TUICalling/Source/TUICallingKit_Enterprise/TUICallingKit.h new file mode 100644 index 000000000..438eadf19 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/TUICallingKit_Enterprise/TUICallingKit.h @@ -0,0 +1,13 @@ +// +// TUICallingKit.h +// Pods +// +// Created by gg on 2021/5/18. +// + +#ifndef TUICallingKit_h +#define TUICallingKit_h + +@import TXLiteAVSDK_Enterprise; + +#endif /* TUICallingKit_h */ diff --git a/iOS/TUIKit/TUICalling/Source/TUICallingKit_Professional/TUICallingKit.h b/iOS/TUIKit/TUICalling/Source/TUICallingKit_Professional/TUICallingKit.h new file mode 100644 index 000000000..b6eb3d572 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/TUICallingKit_Professional/TUICallingKit.h @@ -0,0 +1,13 @@ +// +// TUICallingKit.h +// Pods +// +// Created by gg on 2021/5/18. +// + +#ifndef TUICallingKit_h +#define TUICallingKit_h + +@import TXLiteAVSDK_Professional; + +#endif /* TUICallingKit_h */ diff --git a/iOS/TUIKit/TUICalling/iOS/Source/TUICallingKit_TRTC/TUICallingKit.h b/iOS/TUIKit/TUICalling/Source/TUICallingKit_TRTC/TUICallingKit.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/TUICallingKit_TRTC/TUICallingKit.h rename to iOS/TUIKit/TUICalling/Source/TUICallingKit_TRTC/TUICallingKit.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Base/TUICallingBaseView.h b/iOS/TUIKit/TUICalling/Source/UI/Base/TUICallingBaseView.h similarity index 93% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Base/TUICallingBaseView.h rename to iOS/TUIKit/TUICalling/Source/UI/Base/TUICallingBaseView.h index be7aad79f..d08e86637 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/Base/TUICallingBaseView.h +++ b/iOS/TUIKit/TUICalling/Source/UI/Base/TUICallingBaseView.h @@ -6,6 +6,7 @@ // #import +#import #import #import #import "TRTCCalling.h" @@ -40,11 +41,11 @@ typedef NS_ENUM(NSUInteger, TUICallingState) { /// 是否是被呼叫方 @property (nonatomic, assign) BOOL isCallee; -/// 是否是自定义视图 -@property (nonatomic, assign) BOOL disableCustomView; - @property (nonatomic, weak) id actionDelegate; +/// 记录本地用户 +@property (nonatomic, strong) CallUserModel *currentUser; + /// 页面相关处理 - (void)show; diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Base/TUICallingBaseView.m b/iOS/TUIKit/TUICalling/Source/UI/Base/TUICallingBaseView.m similarity index 80% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Base/TUICallingBaseView.m rename to iOS/TUIKit/TUICalling/Source/UI/Base/TUICallingBaseView.m index af7cbec6e..4a6829cf9 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/Base/TUICallingBaseView.m +++ b/iOS/TUIKit/TUICalling/Source/UI/Base/TUICallingBaseView.m @@ -24,7 +24,6 @@ } - (void)show { - self.disableCustomView = YES; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.window.windowLevel = UIWindowLevelAlert + 1; self.window.backgroundColor = [UIColor clearColor]; @@ -32,7 +31,7 @@ if (@available(iOS 13.0, *)) { for (UIWindowScene *windowScene in [UIApplication sharedApplication].connectedScenes) { - if (windowScene.activationState == UISceneActivationStateForegroundActive) { + if (windowScene.activationState == UISceneActivationStateForegroundActive || windowScene.activationState == UISceneActivationStateBackground) { self.window.windowScene = windowScene; break; } @@ -43,11 +42,9 @@ } - (void)disMiss { - if (self.disableCustomView) { - [self removeFromSuperview]; - self.window.hidden = YES; - self.window = nil; - } + [self removeFromSuperview]; + self.window.hidden = YES; + self.window = nil; } - (void)configViewWithUserList:(NSArray *)userList sponsor:(CallUserModel *)sponsor { @@ -79,6 +76,15 @@ } - (void)setCallingTimeStr:(NSString *)timeStr { + NSCAssert(NO, @"%s must be overridden by subclasses", __func__); +} + +#pragma mark - Getter and setter + +- (void)setCurrentUser:(CallUserModel *)currentUser { + if (currentUser) { + _currentUser = currentUser; + } } @end diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TRTCGCDTimer.h b/iOS/TUIKit/TUICalling/Source/UI/Common/TRTCGCDTimer.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TRTCGCDTimer.h rename to iOS/TUIKit/TUICalling/Source/UI/Common/TRTCGCDTimer.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TRTCGCDTimer.m b/iOS/TUIKit/TUICalling/Source/UI/Common/TRTCGCDTimer.m similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TRTCGCDTimer.m rename to iOS/TUIKit/TUICalling/Source/UI/Common/TRTCGCDTimer.m diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUICallingAudioPlayer.h b/iOS/TUIKit/TUICalling/Source/UI/Common/TUICallingAudioPlayer.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUICallingAudioPlayer.h rename to iOS/TUIKit/TUICalling/Source/UI/Common/TUICallingAudioPlayer.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUICallingAudioPlayer.m b/iOS/TUIKit/TUICalling/Source/UI/Common/TUICallingAudioPlayer.m similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUICallingAudioPlayer.m rename to iOS/TUIKit/TUICalling/Source/UI/Common/TUICallingAudioPlayer.m diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUICommonUtil.h b/iOS/TUIKit/TUICalling/Source/UI/Common/TUICommonUtil.h similarity index 94% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUICommonUtil.h rename to iOS/TUIKit/TUICalling/Source/UI/Common/TUICommonUtil.h index ed02756e9..6b24f424e 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUICommonUtil.h +++ b/iOS/TUIKit/TUICalling/Source/UI/Common/TUICommonUtil.h @@ -6,9 +6,12 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN +FOUNDATION_EXPORT void TRTCCloudCallingAPILog(NSString *format, ...); + @interface TUICommonUtil : NSObject + (NSBundle *)callingBundle; diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUICommonUtil.m b/iOS/TUIKit/TUICalling/Source/UI/Common/TUICommonUtil.m similarity index 84% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUICommonUtil.m rename to iOS/TUIKit/TUICalling/Source/UI/Common/TUICommonUtil.m index dce754bb1..7c463f4de 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUICommonUtil.m +++ b/iOS/TUIKit/TUICalling/Source/UI/Common/TUICommonUtil.m @@ -6,6 +6,27 @@ // #import "TUICommonUtil.h" +#import "TRTCCloud.h" + +@interface TRTCCloud (CallingLog) + +// 打印一些关键log到本地日志中 +- (void)apiLog:(NSString *)log; + +@end + +void TRTCCloudCallingAPILog(NSString *format, ...) { + if (!format || ![format isKindOfClass:[NSString class]] || format.length == 0) { + return; + } + va_list arguments; + va_start(arguments, format); + NSString *content = [[NSString alloc] initWithFormat:format arguments:arguments]; + va_end(arguments); + if (content) { + [[TRTCCloud sharedInstance] apiLog:content]; + } +} @implementation TUICommonUtil diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUIGradientView.h b/iOS/TUIKit/TUICalling/Source/UI/Common/TUIGradientView.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUIGradientView.h rename to iOS/TUIKit/TUICalling/Source/UI/Common/TUIGradientView.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUIGradientView.m b/iOS/TUIKit/TUICalling/Source/UI/Common/TUIGradientView.m similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/TUIGradientView.m rename to iOS/TUIKit/TUICalling/Source/UI/Common/TUIGradientView.m diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/UIButton+TUIImageTitleSpacing.h b/iOS/TUIKit/TUICalling/Source/UI/Common/UIButton+TUIImageTitleSpacing.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/UIButton+TUIImageTitleSpacing.h rename to iOS/TUIKit/TUICalling/Source/UI/Common/UIButton+TUIImageTitleSpacing.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/UIButton+TUIImageTitleSpacing.m b/iOS/TUIKit/TUICalling/Source/UI/Common/UIButton+TUIImageTitleSpacing.m similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/UIButton+TUIImageTitleSpacing.m rename to iOS/TUIKit/TUICalling/Source/UI/Common/UIButton+TUIImageTitleSpacing.m diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/UIColor+TUIHex.h b/iOS/TUIKit/TUICalling/Source/UI/Common/UIColor+TUIHex.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/UIColor+TUIHex.h rename to iOS/TUIKit/TUICalling/Source/UI/Common/UIColor+TUIHex.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/UIColor+TUIHex.m b/iOS/TUIKit/TUICalling/Source/UI/Common/UIColor+TUIHex.m similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/UIColor+TUIHex.m rename to iOS/TUIKit/TUICalling/Source/UI/Common/UIColor+TUIHex.m diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/UIView+TUIEX.h b/iOS/TUIKit/TUICalling/Source/UI/Common/UIView+TUIEX.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/UIView+TUIEX.h rename to iOS/TUIKit/TUICalling/Source/UI/Common/UIView+TUIEX.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Common/UIView+TUIEX.m b/iOS/TUIKit/TUICalling/Source/UI/Common/UIView+TUIEX.m similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Common/UIView+TUIEX.m rename to iOS/TUIKit/TUICalling/Source/UI/Common/UIView+TUIEX.m diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Manager/TUICalleeDelegateManager.h b/iOS/TUIKit/TUICalling/Source/UI/Manager/TUICalleeDelegateManager.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Manager/TUICalleeDelegateManager.h rename to iOS/TUIKit/TUICalling/Source/UI/Manager/TUICalleeDelegateManager.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Manager/TUICalleeDelegateManager.m b/iOS/TUIKit/TUICalling/Source/UI/Manager/TUICalleeDelegateManager.m similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Manager/TUICalleeDelegateManager.m rename to iOS/TUIKit/TUICalling/Source/UI/Manager/TUICalleeDelegateManager.m diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Manager/TUICallingDelegateManager.h b/iOS/TUIKit/TUICalling/Source/UI/Manager/TUICallingDelegateManager.h similarity index 96% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Manager/TUICallingDelegateManager.h rename to iOS/TUIKit/TUICalling/Source/UI/Manager/TUICallingDelegateManager.h index 3ca93117f..cd5ef8acc 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/Manager/TUICallingDelegateManager.h +++ b/iOS/TUIKit/TUICalling/Source/UI/Manager/TUICallingDelegateManager.h @@ -6,6 +6,7 @@ // #import +#import #import "TRTCCallingModel.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Manager/TUICallingDelegateManager.m b/iOS/TUIKit/TUICalling/Source/UI/Manager/TUICallingDelegateManager.m similarity index 98% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Manager/TUICallingDelegateManager.m rename to iOS/TUIKit/TUICalling/Source/UI/Manager/TUICallingDelegateManager.m index 0088810e1..7904d6541 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/Manager/TUICallingDelegateManager.m +++ b/iOS/TUIKit/TUICalling/Source/UI/Manager/TUICallingDelegateManager.m @@ -30,7 +30,7 @@ }]; if (index >= 0) { TUICallingGroupCell *cell = (TUICallingGroupCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]]; - return cell.avatarImageView; + return cell.renderView; } return nil; } diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Protocol/TUICallingActionProtocol.h b/iOS/TUIKit/TUICalling/Source/UI/Protocol/TUICallingActionProtocol.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Protocol/TUICallingActionProtocol.h rename to iOS/TUIKit/TUICalling/Source/UI/Protocol/TUICallingActionProtocol.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Protocol/TUIInvitedActionProtocal.h b/iOS/TUIKit/TUICalling/Source/UI/Protocol/TUIInvitedActionProtocal.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Protocol/TUIInvitedActionProtocal.h rename to iOS/TUIKit/TUICalling/Source/UI/Protocol/TUIInvitedActionProtocal.h diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/Contents.json b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/calling_mic.imageset/Contents.json b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/calling_mic.imageset/Contents.json new file mode 100644 index 000000000..8850b69c6 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/calling_mic.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "calling_mic@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "calling_mic@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/calling_mic.imageset/calling_mic@2x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/calling_mic.imageset/calling_mic@2x.png new file mode 100644 index 000000000..82890b4d4 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/calling_mic.imageset/calling_mic@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/calling_mic.imageset/calling_mic@3x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/calling_mic.imageset/calling_mic@3x.png new file mode 100644 index 000000000..62ad25e8e Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/calling_mic.imageset/calling_mic@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_off.imageset/Contents.json b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_off.imageset/Contents.json new file mode 100644 index 000000000..c54426686 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_off.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "camera_off@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "camera_off@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_off.imageset/camera_off@2x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_off.imageset/camera_off@2x.png new file mode 100644 index 000000000..2093ad204 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_off.imageset/camera_off@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_off.imageset/camera_off@3x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_off.imageset/camera_off@3x.png new file mode 100644 index 000000000..112d94fb3 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_off.imageset/camera_off@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_on.imageset/Contents.json b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_on.imageset/Contents.json new file mode 100644 index 000000000..e0d8309e4 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_on.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "camera_on@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "camera_on@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_on.imageset/camera_on@2x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_on.imageset/camera_on@2x.png new file mode 100644 index 000000000..c4c00b060 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_on.imageset/camera_on@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_on.imageset/camera_on@3x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_on.imageset/camera_on@3x.png new file mode 100644 index 000000000..65af2eea7 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/camera_on.imageset/camera_on@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree.imageset/Contents.json b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree.imageset/Contents.json new file mode 100644 index 000000000..df1bd23ae --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_handsfree@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_handsfree@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree.imageset/ic_handsfree@2x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree.imageset/ic_handsfree@2x.png new file mode 100644 index 000000000..4042495ae Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree.imageset/ic_handsfree@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree.imageset/ic_handsfree@3x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree.imageset/ic_handsfree@3x.png new file mode 100644 index 000000000..8c4ba6ee7 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree.imageset/ic_handsfree@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree_on.imageset/Contents.json b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree_on.imageset/Contents.json new file mode 100644 index 000000000..c3dd2363d --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree_on.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_handsfree_on@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_handsfree_on@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@2x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@2x.png new file mode 100644 index 000000000..5ec86cab7 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@3x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@3x.png new file mode 100644 index 000000000..5f49e9f56 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_handsfree_on.imageset/ic_handsfree_on@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_hangup.imageset/Contents.json b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_hangup.imageset/Contents.json new file mode 100644 index 000000000..369f5b410 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_hangup.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_hangup@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_hangup@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_hangup.imageset/ic_hangup@2x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_hangup.imageset/ic_hangup@2x.png new file mode 100644 index 000000000..24341516f Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_hangup.imageset/ic_hangup@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_hangup.imageset/ic_hangup@3x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_hangup.imageset/ic_hangup@3x.png new file mode 100644 index 000000000..1c6d506df Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_hangup.imageset/ic_hangup@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute.imageset/Contents.json b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute.imageset/Contents.json new file mode 100644 index 000000000..4ee199413 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_mute@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_mute@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute.imageset/ic_mute@2x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute.imageset/ic_mute@2x.png new file mode 100644 index 000000000..8a13b6d9c Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute.imageset/ic_mute@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute.imageset/ic_mute@3x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute.imageset/ic_mute@3x.png new file mode 100644 index 000000000..dd57d7249 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute.imageset/ic_mute@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute_on.imageset/Contents.json b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute_on.imageset/Contents.json new file mode 100644 index 000000000..9a46c969e --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute_on.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_mute_on@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_mute_on@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute_on.imageset/ic_mute_on@2x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute_on.imageset/ic_mute_on@2x.png new file mode 100644 index 000000000..f0b99788c Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute_on.imageset/ic_mute_on@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute_on.imageset/ic_mute_on@3x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute_on.imageset/ic_mute_on@3x.png new file mode 100644 index 000000000..8166ee776 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/ic_mute_on.imageset/ic_mute_on@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch2audio.imageset/Contents.json b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch2audio.imageset/Contents.json new file mode 100644 index 000000000..431b24b34 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch2audio.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "switch2audio@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "switch2audio@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch2audio.imageset/switch2audio@2x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch2audio.imageset/switch2audio@2x.png new file mode 100644 index 000000000..1bbcea7d4 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch2audio.imageset/switch2audio@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch2audio.imageset/switch2audio@3x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch2audio.imageset/switch2audio@3x.png new file mode 100644 index 000000000..77026c4db Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch2audio.imageset/switch2audio@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch_camera.imageset/Contents.json b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch_camera.imageset/Contents.json new file mode 100644 index 000000000..ce6df97fe --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch_camera.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "switch_camera@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "switch_camera@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch_camera.imageset/switch_camera@2x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch_camera.imageset/switch_camera@2x.png new file mode 100644 index 000000000..615991967 Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch_camera.imageset/switch_camera@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch_camera.imageset/switch_camera@3x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch_camera.imageset/switch_camera@3x.png new file mode 100644 index 000000000..6ed2c4f8f Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/switch_camera.imageset/switch_camera@3x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/trtccalling_ic_dialing.imageset/Contents.json b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/trtccalling_ic_dialing.imageset/Contents.json new file mode 100644 index 000000000..4a220aced --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/trtccalling_ic_dialing.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "trtccalling_ic_dialing@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "trtccalling_ic_dialing@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/trtccalling_ic_dialing.imageset/trtccalling_ic_dialing@2x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/trtccalling_ic_dialing.imageset/trtccalling_ic_dialing@2x.png new file mode 100644 index 000000000..c762da9ce Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/trtccalling_ic_dialing.imageset/trtccalling_ic_dialing@2x.png differ diff --git a/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/trtccalling_ic_dialing.imageset/trtccalling_ic_dialing@3x.png b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/trtccalling_ic_dialing.imageset/trtccalling_ic_dialing@3x.png new file mode 100644 index 000000000..c762da9ce Binary files /dev/null and b/iOS/TUIKit/TUICalling/Source/UI/TUICallingAssets.xcassets/trtccalling_ic_dialing.imageset/trtccalling_ic_dialing@3x.png differ diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/TUICallingView.h b/iOS/TUIKit/TUICalling/Source/UI/TUICallingView.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/TUICallingView.h rename to iOS/TUIKit/TUICalling/Source/UI/TUICallingView.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/TUICallingView.m b/iOS/TUIKit/TUICalling/Source/UI/TUICallingView.m similarity index 96% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/TUICallingView.m rename to iOS/TUIKit/TUICalling/Source/UI/TUICallingView.m index 8b42b8248..5a8651240 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/TUICallingView.m +++ b/iOS/TUIKit/TUICalling/Source/UI/TUICallingView.m @@ -102,6 +102,7 @@ static CGFloat const kSmallVideoViewWidth = 100.0f; - (void)setupUI { self.backgroundColor = [UIColor t_colorWithHexString:@"#F4F5F9"]; if (self.isVideo) { + self.backgroundColor = [UIColor t_colorWithHexString:@"#242424"]; // 设置背景视频试图 [[TRTCCalling shareInstance] closeCamara]; [[TRTCCalling shareInstance] openCamera:self.isFrontCamera view:self.localPreView]; @@ -272,10 +273,11 @@ static CGFloat const kSmallVideoViewWidth = 100.0f; - (void)switchToAudio { self.isVideo = NO; self.isClearFlag = YES; + self.backgroundColor = [UIColor t_colorWithHexString:@"#F4F5F9"]; [[TRTCCalling shareInstance] closeCamara]; if (self.isHandsFreeOn) { - [self hangsfreeTouchEvent:nil isShowToast:NO]; + [self hangsfreeTouchEvent:nil]; } [self setCurCallingState:self.curCallingState]; } @@ -424,6 +426,8 @@ static CGFloat const kSmallVideoViewWidth = 100.0f; } - (void)updateUser:(CallUserModel *)user animated:(BOOL)animated { + self.remotePreView.hidden = !user.isVideoAvaliable; + if (self.remoteSponsor.userId == user.userId) { self.remoteSponsor = user; } @@ -464,7 +468,6 @@ static CGFloat const kSmallVideoViewWidth = 100.0f; self.isMicMute = !self.isMicMute; [[TRTCCalling shareInstance] setMicMute:self.isMicMute]; [self.muteBtn configBackgroundImage:[TUICommonUtil getBundleImageWithName:self.isMicMute ? @"ic_mute_on" : @"ic_mute"]]; - [self makeToast:self.isMicMute ? CallingLocalize(@"Demo.TRTC.calling.muteon") : CallingLocalize(@"Demo.TRTC.calling.muteoff")]; } - (void)hangupTouchEvent:(UIButton *)sender { @@ -474,17 +477,9 @@ static CGFloat const kSmallVideoViewWidth = 100.0f; } - (void)hangsfreeTouchEvent:(UIButton *)sender { - [self hangsfreeTouchEvent:sender isShowToast:YES]; -} - -- (void)hangsfreeTouchEvent:(UIButton *)sender isShowToast:(BOOL)isShowToast { self.isHandsFreeOn = !self.isHandsFreeOn; [[TRTCCalling shareInstance] setHandsFree:self.isHandsFreeOn]; [self.handsfreeBtn configBackgroundImage:[TUICommonUtil getBundleImageWithName:self.isHandsFreeOn ? @"ic_handsfree_on" : @"ic_handsfree"]]; - - if (isShowToast) { - [self makeToast:self.isHandsFreeOn ? CallingLocalize(@"Demo.TRTC.calling.handsfreeon") : CallingLocalize(@"Demo.TRTC.calling.handsfreeoff")]; - } } - (void)switchCameraTouchEvent:(UIButton *)sender { @@ -500,8 +495,10 @@ static CGFloat const kSmallVideoViewWidth = 100.0f; if (self.isCloseCamera) { [[TRTCCalling shareInstance] closeCamara]; + self.localPreView.hidden = YES; } else { [[TRTCCalling shareInstance] openCamera:self.isFrontCamera view:self.localPreView]; + self.localPreView.hidden = NO; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @@ -560,10 +557,7 @@ static CGFloat const kSmallVideoViewWidth = 100.0f; [self.localPreView setUserInteractionEnabled:NO]; [remoteView setUserInteractionEnabled:NO]; } else { - if (self.localPreView.isHidden || remoteView.isHidden) { - [self.localPreView setUserInteractionEnabled:YES]; - [remoteView setUserInteractionEnabled:YES]; - } + [self.localPreView setUserInteractionEnabled:YES]; } }]; } else { @@ -580,10 +574,7 @@ static CGFloat const kSmallVideoViewWidth = 100.0f; [self.localPreView setUserInteractionEnabled:NO]; [remoteView setUserInteractionEnabled:NO]; } else { - if (self.localPreView.isHidden || remoteView.isHidden) { - [self.localPreView setUserInteractionEnabled:YES]; - [remoteView setUserInteractionEnabled:YES]; - } + [remoteView setUserInteractionEnabled:YES]; } }]; } diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/TUIGroupCallingView.h b/iOS/TUIKit/TUICalling/Source/UI/TUIGroupCallingView.h similarity index 74% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/TUIGroupCallingView.h rename to iOS/TUIKit/TUICalling/Source/UI/TUIGroupCallingView.h index 2774900d2..8d4810b01 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/TUIGroupCallingView.h +++ b/iOS/TUIKit/TUICalling/Source/UI/TUIGroupCallingView.h @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN @interface TUIGroupCallingView : TUICallingBaseView /// 初始化页面 -- (instancetype)initWithUser:(CallUserModel *)user isVideo:(BOOL)isVideo isCallee:(BOOL)isCallee; +- (instancetype)initWithIsVideo:(BOOL)isVideo isCallee:(BOOL)isCallee; @end diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/TUIGroupCallingView.m b/iOS/TUIKit/TUICalling/Source/UI/TUIGroupCallingView.m similarity index 82% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/TUIGroupCallingView.m rename to iOS/TUIKit/TUICalling/Source/UI/TUIGroupCallingView.m index b8dc865c0..1fcc4ce92 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/TUIGroupCallingView.m +++ b/iOS/TUIKit/TUICalling/Source/UI/TUIGroupCallingView.m @@ -41,6 +41,9 @@ /// 接听控制视图 @property (nonatomic, strong) TUIInvitedContainerView *invitedContainerView; +/// 通话时间按钮 +@property (nonatomic, strong) UILabel *callingTime; + /// 关闭麦克风按钮 @property (nonatomic, strong) TUICallingControlButton *muteBtn; @@ -56,9 +59,6 @@ /// 切换摄像头 @property (nonatomic, strong) UIButton *switchCameraBtn; -/// 记录本地用户 -@property (nonatomic, strong) CallUserModel *currentUser; - /// 记录发起通话着 @property (nonatomic, strong) CallUserModel *curSponsor; @@ -72,11 +72,10 @@ @implementation TUIGroupCallingView -- (instancetype)initWithUser:(CallUserModel *)user isVideo:(BOOL)isVideo isCallee:(BOOL)isCallee { +- (instancetype)initWithIsVideo:(BOOL)isVideo isCallee:(BOOL)isCallee{ self = [super initWithFrame:[UIScreen mainScreen].bounds]; if (self) { - self.currentUser = user; - self.userList = [NSMutableArray arrayWithObject:user]; + self.userList = [NSMutableArray array]; self.isVideo = isVideo; self.isCallee = isCallee; self.isFrontCamera = YES; @@ -95,19 +94,69 @@ _curCallingState = self.isCallee ? TUICallingStateOnInvitee : TUICallingStateDailing; } +- (void)initUIForCaller { + if (self.isVideo) { + [self initUIForVideoCaller]; + } else { + [self initUIForAudioCaller]; + } +} + /// 多人通话,主叫方/接听后 UI初始化 - (void)initUIForAudioCaller { [self addSubview:self.groupCollectionView]; + [self addSubview:self.callingTime]; [self addSubview:self.muteBtn]; - [self addSubview:self.handsfreeBtn]; - [self addSubview:self.closeCameraBtn]; [self addSubview:self.hangupBtn]; + [self addSubview:self.handsfreeBtn]; // 视图约束 [self.groupCollectionView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self).offset(StatusBar_Height); make.centerX.equalTo(self); make.width.height.mas_equalTo(self.bounds.size.width); }]; + [self.callingTime mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.equalTo(self); + make.bottom.equalTo(self.hangupBtn.mas_top).offset(-10); + make.height.equalTo(@(30)); + }]; + [self.muteBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.equalTo(self.hangupBtn.mas_left).offset(-5); + make.centerY.equalTo(self.hangupBtn); + make.size.equalTo(@(kControlBtnSize)); + }]; + [self.hangupBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.equalTo(self); + make.bottom.equalTo(self.mas_top).offset(self.frame.size.height - Bottom_SafeHeight - 20); + make.size.equalTo(@(kControlBtnSize)); + }]; + [self.handsfreeBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.hangupBtn.mas_right).offset(5); + make.centerY.equalTo(self.hangupBtn); + make.size.equalTo(@(kControlBtnSize)); + }]; +} + +/// 多人通话,主叫方/接听后 UI初始化 +- (void)initUIForVideoCaller { + [self addSubview:self.groupCollectionView]; + [self addSubview:self.callingTime]; + [self addSubview:self.muteBtn]; + [self addSubview:self.handsfreeBtn]; + [self addSubview:self.closeCameraBtn]; + [self addSubview:self.hangupBtn]; + [self addSubview:self.switchCameraBtn]; + // 视图约束 + [self.groupCollectionView mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self).offset(StatusBar_Height); + make.centerX.equalTo(self); + make.width.height.mas_equalTo(self.bounds.size.width); + }]; + [self.callingTime mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.equalTo(self); + make.bottom.equalTo(self.handsfreeBtn.mas_top).offset(-10); + make.height.equalTo(@(30)); + }]; [self.muteBtn mas_makeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(self.handsfreeBtn.mas_left); make.centerY.equalTo(self.handsfreeBtn); @@ -128,6 +177,11 @@ make.bottom.equalTo(self.mas_top).offset(self.frame.size.height - Bottom_SafeHeight - 20); make.size.equalTo(@(kControlBtnSize)); }]; + [self.switchCameraBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.equalTo(self.hangupBtn); + make.left.equalTo(self.hangupBtn.mas_right).offset(20); + make.size.equalTo(@(CGSizeMake(36, 36))); + }]; } /// 多人通话,被呼叫方UI初始化 @@ -176,13 +230,15 @@ [self.userContainerView configUserInfoViewWith:self.curSponsor showWaitingText:waitingText]; } break; case TUICallingStateDailing: { - [self initUIForAudioCaller]; + [self initUIForCaller]; + self.callingTime.hidden = YES; [self.userContainerView configUserInfoViewWith:self.curSponsor showWaitingText:CallingLocalize(@"Demo.TRTC.calling.waitaccept")]; } break; case TUICallingStateCalling: { - [self initUIForAudioCaller]; - self.switchCameraBtn.hidden = NO; + [self initUIForCaller]; + self.callingTime.hidden = NO; self.userContainerView.hidden = YES; + [self handleLocalRenderView]; } break; default: break; @@ -218,6 +274,9 @@ [self.delegateManager reloadCallingGroupWithModel:self.userList]; [self.groupCollectionView reloadData]; + [self.groupCollectionView layoutIfNeeded]; + self.switchCameraBtn.hidden = !self.isVideo; + [self handleLocalRenderView]; } - (void)enterUser:(CallUserModel *)user { @@ -227,6 +286,7 @@ if (index < 0) return; self.curCallingState = TUICallingStateCalling; + user.isEnter = YES; self.userList[index] = user; [self.delegateManager reloadCallingGroupWithModel:self.userList]; [self.delegateManager reloadGroupCellWithIndex:index]; @@ -234,7 +294,6 @@ if (self.isVideo) { UIView *renderView = [self.delegateManager getRenderViewFromUser:user.userId]; [[TRTCCalling shareInstance] startRemoteView:user.userId view:renderView]; - [self showLocalRenderView]; } } @@ -292,14 +351,27 @@ self.curCallingState = TUICallingStateCalling; } +- (void)setCallingTimeStr:(NSString *)timeStr { + if (timeStr && timeStr.length > 0) { + self.callingTime.text = timeStr; + } +} + #pragma mark - Private -- (void)showLocalRenderView { +- (void)handleLocalRenderView { + if (!self.isVideo) return; + UIView *localRenderView = [self.delegateManager getRenderViewFromUser:self.currentUser.userId]; if (!self.isCloseCamera && localRenderView != nil) { [[TRTCCalling shareInstance] openCamera:self.isFrontCamera view:localRenderView]; } + + self.currentUser.isVideoAvaliable = !self.isCloseCamera; + self.currentUser.isEnter = YES; + self.currentUser.isAudioAvaliable = YES; + [self updateUser:self.currentUser animated:NO]; } - (void)clearAllSubViews { @@ -338,7 +410,8 @@ self.isMicMute = !self.isMicMute; [[TRTCCalling shareInstance] setMicMute:self.isMicMute]; [self.muteBtn configBackgroundImage:[TUICommonUtil getBundleImageWithName:self.isMicMute ? @"ic_mute_on" : @"ic_mute"]]; - [self makeToast:self.isMicMute ? CallingLocalize(@"Demo.TRTC.calling.muteon") : CallingLocalize(@"Demo.TRTC.calling.muteoff")]; + self.currentUser.isAudioAvaliable = !self.isMicMute; + [self updateUser:self.currentUser animated:NO]; } - (void)hangupTouchEvent:(UIButton *)sender { @@ -351,7 +424,6 @@ self.isHandsFreeOn = !self.isHandsFreeOn; [[TRTCCalling shareInstance] setHandsFree:self.isHandsFreeOn]; [self.handsfreeBtn configBackgroundImage:[TUICommonUtil getBundleImageWithName:self.isHandsFreeOn ? @"ic_handsfree_on" : @"ic_handsfree"]]; - [self makeToast:self.isHandsFreeOn ? CallingLocalize(@"Demo.TRTC.calling.handsfreeon") : CallingLocalize(@"Demo.TRTC.calling.handsfreeoff")]; } - (void)switchCameraTouchEvent:(UIButton *)sender { @@ -366,16 +438,26 @@ if (self.isCloseCamera) { [[TRTCCalling shareInstance] closeCamara]; + self.switchCameraBtn.hidden = YES; } else { - [self showLocalRenderView]; + self.switchCameraBtn.hidden = NO; } + [self handleLocalRenderView]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.closeCameraBtn setUserInteractionEnabled:YES]; }); } -#pragma mark - Lazy +#pragma mark - Getter And Setter + +- (void)setCurrentUser:(CallUserModel *)currentUser { + [super setCurrentUser:currentUser]; + + if (currentUser) { + [self.userList insertObject:currentUser atIndex:0]; + } +} - (TUIAudioUserContainerView *)userContainerView { if (!_userContainerView) { @@ -385,6 +467,19 @@ return _userContainerView; } +- (UILabel *)callingTime { + if (!_callingTime) { + _callingTime = [[UILabel alloc] initWithFrame:CGRectZero]; + _callingTime.font = [UIFont boldSystemFontOfSize:14.0f]; + [_callingTime setTextColor:[UIColor t_colorWithHexString:@"#FFFFFF"]]; + [_callingTime setBackgroundColor:[UIColor clearColor]]; + [_callingTime setText:@"00:00"]; + _callingTime.hidden = YES; + [_callingTime setTextAlignment:NSTextAlignmentCenter]; + } + return _callingTime; +} + - (TUICallingControlButton *)muteBtn { if (!_muteBtn) { __weak typeof(self) weakSelf = self; diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIAudioUserContainerView.h b/iOS/TUIKit/TUICalling/Source/UI/Views/TUIAudioUserContainerView.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIAudioUserContainerView.h rename to iOS/TUIKit/TUICalling/Source/UI/Views/TUIAudioUserContainerView.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIAudioUserContainerView.m b/iOS/TUIKit/TUICalling/Source/UI/Views/TUIAudioUserContainerView.m similarity index 94% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIAudioUserContainerView.m rename to iOS/TUIKit/TUICalling/Source/UI/Views/TUIAudioUserContainerView.m index df3838e37..71b5043e5 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIAudioUserContainerView.m +++ b/iOS/TUIKit/TUICalling/Source/UI/Views/TUIAudioUserContainerView.m @@ -11,6 +11,7 @@ #import #import "UIColor+TUIHex.h" #import "CallingLocalized.h" +#import "TUICommonUtil.h" @interface TUIAudioUserContainerView () @@ -66,8 +67,8 @@ } - (void)configUserInfoViewWith:(CallUserModel *)userModel showWaitingText:(NSString *)text { - [self.userHeadImageView sd_setImageWithURL:[NSURL URLWithString:userModel.avatar] placeholderImage:[UIColor imageWithColor:[UIColor t_colorWithHexString:@"#E3E3E3"] andSize:CGSizeMake(1, 1)]]; - [_userNameLabel setText:userModel.name]; + [self.userHeadImageView sd_setImageWithURL:[NSURL URLWithString:userModel.avatar] placeholderImage:[TUICommonUtil getBundleImageWithName:@"userIcon"]]; + [_userNameLabel setText:userModel.name ?: userModel.userId]; self.waitingInviteLabel.text = text; } diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICalleeGroupCell.h b/iOS/TUIKit/TUICalling/Source/UI/Views/TUICalleeGroupCell.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICalleeGroupCell.h rename to iOS/TUIKit/TUICalling/Source/UI/Views/TUICalleeGroupCell.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICalleeGroupCell.m b/iOS/TUIKit/TUICalling/Source/UI/Views/TUICalleeGroupCell.m similarity index 87% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICalleeGroupCell.m rename to iOS/TUIKit/TUICalling/Source/UI/Views/TUICalleeGroupCell.m index 03c4fc55c..8a05e6cb2 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICalleeGroupCell.m +++ b/iOS/TUIKit/TUICalling/Source/UI/Views/TUICalleeGroupCell.m @@ -9,6 +9,8 @@ #import #import #import "UIColor+TUIHex.h" +#import "TUICommonUtil.h" +#import "TUICallingConstants.h" @interface TUICalleeGroupCell () @@ -20,7 +22,7 @@ - (void)setModel:(CallUserModel *)model { _model = model; - [self.userIcon sd_setImageWithURL:[NSURL URLWithString:model.avatar] placeholderImage:[UIColor imageWithColor:[UIColor t_colorWithHexString:@"#999999"] andSize:CGSizeMake(1, 1)]]; + [self.userIcon sd_setImageWithURL:[NSURL URLWithString:model.avatar] placeholderImage:[TUICommonUtil getBundleImageWithName:@"userIcon"]]; } - (instancetype)initWithFrame:(CGRect)frame { diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingControlButton.h b/iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingControlButton.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingControlButton.h rename to iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingControlButton.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingControlButton.m b/iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingControlButton.m similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingControlButton.m rename to iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingControlButton.m diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingGroupCell.h b/iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingGroupCell.h similarity index 83% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingGroupCell.h rename to iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingGroupCell.h index 31740b4ea..f9962c5c5 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingGroupCell.h +++ b/iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingGroupCell.h @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) CallUserModel *model; -@property (nonatomic, weak) UIImageView *avatarImageView; +@property (nonatomic, weak) UIView *renderView; @end diff --git a/iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingGroupCell.m b/iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingGroupCell.m new file mode 100644 index 000000000..bcf396216 --- /dev/null +++ b/iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingGroupCell.m @@ -0,0 +1,132 @@ +// +// TUICallingGroupCell.m +// TUICalling +// +// Created by noah on 2021/8/24. +// + +#import "TUICallingGroupCell.h" +#import +#import +#import "UIColor+TUIHex.h" +#import "TUICommonUtil.h" + +@interface TUICallingGroupCell () + +/// 用户昵称 +@property (nonatomic, strong) UILabel *titleLabel; + +@property (nonatomic, strong) UIView *bgView; + +@property (nonatomic, strong) UIImageView *avatarImageView; + +@property (nonatomic, strong) UIImageView *volumeImageView; + +@end + +@implementation TUICallingGroupCell + +- (void)setModel:(CallUserModel *)model { + _model = model; + self.titleLabel.text = model.name ?: model.userId; + [self.avatarImageView sd_setImageWithURL:[NSURL URLWithString:model.avatar] placeholderImage:[TUICommonUtil getBundleImageWithName:@"userIcon"]]; + self.avatarImageView.hidden = model.isVideoAvaliable; + self.bgView.hidden = model.isEnter || model.isVideoAvaliable || model.isAudioAvaliable; + + // 处理麦克风图标问题 + if (model.isAudioAvaliable) { + [self.volumeImageView setImage:[TUICommonUtil getBundleImageWithName:@"ic_mute"]]; + + if (model.volume >= 0.30) { + self.volumeImageView.hidden = NO; + } else { + self.volumeImageView.hidden = YES; + } + } else { + self.volumeImageView.hidden = YES; + } +} + +- (NSBundle *)callingBundle { + NSURL *callingKitBundleURL = [[NSBundle mainBundle] URLForResource:@"TUICallingKitBundle" withExtension:@"bundle"]; + return [NSBundle bundleWithURL:callingKitBundleURL]; +} + +- (UIImage *)getBundleImageWithName:(NSString *)name { + return [UIImage imageNamed:name inBundle:[self callingBundle] compatibleWithTraitCollection:nil]; +} + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + // renderView + UIView *renderView = [[UIView alloc] initWithFrame:CGRectZero]; + [self.contentView addSubview:renderView]; + self.renderView = renderView; + // avatarImageView + UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectZero]; + imageView.contentMode = UIViewContentModeScaleAspectFill; + [self.contentView addSubview:imageView]; + self.avatarImageView = imageView; + // 蒙层视图 + UIView *bgView = [[UIView alloc] init]; + bgView.backgroundColor = [UIColor t_colorWithHexString:@"#000000" alpha:0.3]; + [self.contentView addSubview:bgView]; + self.bgView = bgView; + + // loadingImageView + NSMutableArray *imageArray = [NSMutableArray array]; + for (int i = 1; i <= 8; i++) { + NSString *iamgeName = [NSString stringWithFormat:@"loading-%d", i]; + [imageArray addObject:[TUICommonUtil getBundleImageWithName:iamgeName]]; + } + + UIImageView *loadingImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; + loadingImageView.contentMode = UIViewContentModeScaleAspectFill; + loadingImageView.animationImages = [imageArray copy]; + loadingImageView.animationDuration = 2; + loadingImageView.animationRepeatCount = 0; + [loadingImageView startAnimating]; + [self.bgView addSubview:loadingImageView]; + + // titleLabel + UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero]; + label.textColor = [UIColor t_colorWithHexString:@"#FFFFFF"]; + label.font = [UIFont systemFontOfSize:12]; + label.textAlignment = NSTextAlignmentLeft; + [self.contentView addSubview:label]; + self.titleLabel = label; + // volumeImageView + UIImageView *volumeImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; + volumeImageView.contentMode = UIViewContentModeScaleAspectFill; + [self.contentView addSubview:volumeImageView]; + self.volumeImageView.hidden = YES; + self.volumeImageView = volumeImageView; + + [renderView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.contentView); + }]; + [imageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.contentView); + }]; + [bgView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.contentView); + }]; + [loadingImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.center.equalTo(self.bgView); + make.height.width.equalTo(@(42)); + }]; + [label mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.contentView).offset(10); + make.bottom.equalTo(self.contentView).offset(-5); + make.right.equalTo(self.volumeImageView.mas_left).offset(-5); + }]; + [volumeImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.bottom.equalTo(self.contentView).offset(-5); + make.centerY.equalTo(label); + make.width.height.equalTo(@(20)); + }]; + } + return self; +} + +@end diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingVideoRenderView.h b/iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingVideoRenderView.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingVideoRenderView.h rename to iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingVideoRenderView.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingVideoRenderView.m b/iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingVideoRenderView.m similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingVideoRenderView.m rename to iOS/TUIKit/TUICalling/Source/UI/Views/TUICallingVideoRenderView.m diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIInvitedContainerView.h b/iOS/TUIKit/TUICalling/Source/UI/Views/TUIInvitedContainerView.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIInvitedContainerView.h rename to iOS/TUIKit/TUICalling/Source/UI/Views/TUIInvitedContainerView.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIInvitedContainerView.m b/iOS/TUIKit/TUICalling/Source/UI/Views/TUIInvitedContainerView.m similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIInvitedContainerView.m rename to iOS/TUIKit/TUICalling/Source/UI/Views/TUIInvitedContainerView.m diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIVideoUserContainerView.h b/iOS/TUIKit/TUICalling/Source/UI/Views/TUIVideoUserContainerView.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIVideoUserContainerView.h rename to iOS/TUIKit/TUICalling/Source/UI/Views/TUIVideoUserContainerView.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIVideoUserContainerView.m b/iOS/TUIKit/TUICalling/Source/UI/Views/TUIVideoUserContainerView.m similarity index 94% rename from iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIVideoUserContainerView.m rename to iOS/TUIKit/TUICalling/Source/UI/Views/TUIVideoUserContainerView.m index f49c5e262..c8c9ef9e3 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUIVideoUserContainerView.m +++ b/iOS/TUIKit/TUICalling/Source/UI/Views/TUIVideoUserContainerView.m @@ -11,6 +11,7 @@ #import "TRTCCallingModel.h" #import "UIColor+TUIHex.h" #import "CallingLocalized.h" +#import "TUICommonUtil.h" @interface TUIVideoUserContainerView () @@ -57,8 +58,8 @@ } - (void)configUserInfoViewWith:(CallUserModel *)userModel showWaitingText:(NSString *)text { - [self.userHeadImageView sd_setImageWithURL:[NSURL URLWithString:userModel.avatar] placeholderImage:[UIColor imageWithColor:[UIColor t_colorWithHexString:@"#999999"] andSize:CGSizeMake(1, 1)]]; - [_userNameLabel setText:userModel.name]; + [self.userHeadImageView sd_setImageWithURL:[NSURL URLWithString:userModel.avatar] placeholderImage:[TUICommonUtil getBundleImageWithName:@"userIcon"]]; + [_userNameLabel setText:userModel.name ?: userModel.userId]; self.waitingInviteLabel.text = text; } diff --git a/iOS/TUIKit/TUICalling/iOS/Source/localized/CallingLocalized.h b/iOS/TUIKit/TUICalling/Source/localized/CallingLocalized.h similarity index 100% rename from iOS/TUIKit/TUICalling/iOS/Source/localized/CallingLocalized.h rename to iOS/TUIKit/TUICalling/Source/localized/CallingLocalized.h diff --git a/iOS/TUIKit/TUICalling/iOS/Source/localized/CallingLocalized.m b/iOS/TUIKit/TUICalling/Source/localized/CallingLocalized.m similarity index 91% rename from iOS/TUIKit/TUICalling/iOS/Source/localized/CallingLocalized.m rename to iOS/TUIKit/TUICalling/Source/localized/CallingLocalized.m index 8f5712fad..83ddab464 100644 --- a/iOS/TUIKit/TUICalling/iOS/Source/localized/CallingLocalized.m +++ b/iOS/TUIKit/TUICalling/Source/localized/CallingLocalized.m @@ -6,12 +6,14 @@ // #import "CallingLocalized.h" +#import "TUIGlobalization.h" #pragma mark - Base NSBundle *CallingBundle(void) { NSURL *callingKitBundleURL = [[NSBundle mainBundle] URLForResource:@"TUICallingKitBundle" withExtension:@"bundle"]; - return [NSBundle bundleWithURL:callingKitBundleURL]; + NSString *path = [[NSBundle bundleWithURL:callingKitBundleURL] pathForResource:[TUIGlobalization tk_localizableLanguageKey] ofType:@"lproj"]; + return [NSBundle bundleWithPath:path]; } NSString *TCLLocalizeFromTable(NSString *key, NSString *table) { diff --git a/iOS/TUIKit/TUICalling/TUICalling.podspec b/iOS/TUIKit/TUICalling/TUICalling.podspec new file mode 100644 index 000000000..5b654bf38 --- /dev/null +++ b/iOS/TUIKit/TUICalling/TUICalling.podspec @@ -0,0 +1,75 @@ +Pod::Spec.new do |spec| + spec.name = 'TUICalling' + spec.version = '9.5' + spec.platform = :ios + spec.ios.deployment_target = '9.0' + spec.license = { :type => 'Proprietary', + :text => <<-LICENSE + copyright 2017 tencent Ltd. All rights reserved. + LICENSE + } + spec.homepage = 'https://cloud.tencent.com/document/product/647' + spec.documentation_url = 'https://cloud.tencent.com/document/product/647' + spec.authors = 'tencent video cloud' + spec.summary = 'TUICalling' + spec.xcconfig = { 'VALID_ARCHS' => 'armv7 arm64 x86_64' } + + spec.dependency 'Masonry' + spec.dependency 'TUICore' + + spec.requires_arc = true + spec.static_framework = true + spec.source = { :git => 'https://github.com/tencentyun/TUICalling.git' } + spec.pod_target_xcconfig = { + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' + } + spec.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' } + spec.swift_version = '5.0' + spec.default_subspec = 'TRTC' + + spec.subspec 'TRTC' do |trtc| + trtc.dependency 'TXLiteAVSDK_TRTC' + framework_path="../../SDK/TXLiteAVSDK_TRTC.framework" + trtc.pod_target_xcconfig={ + 'HEADER_SEARCH_PATHS'=>["$(PODS_TARGET_SRCROOT)/#{framework_path}/Headers"] + } + trtc.source_files = 'Source/*.{h,m,mm}', 'Source/localized/**/*.{h,m,mm}', 'Source/Model/**/*.{h,m,mm}', 'Source/Service/**/*.{h,m,mm}', 'Source/UI/**/*.{h,m,mm}', 'Source/TUICallingKit_TRTC/*.{h,m,mm}' + trtc.ios.framework = ['AVFoundation', 'Accelerate'] + trtc.library = 'c++', 'resolv' + trtc.resource_bundles = { + 'TUICallingKitBundle' => ['Resources/Localized/*', 'Resources/AudioFile', 'Resources/*.xcassets'] + } + end + + spec.subspec 'Enterprise' do |enterprise| + enterprise.dependency 'TXLiteAVSDK_Enterprise' + framework_path="../../SDK/TXLiteAVSDK_Enterprise.framework" + enterprise.pod_target_xcconfig={ + 'HEADER_SEARCH_PATHS'=>["$(PODS_TARGET_SRCROOT)/#{framework_path}/Headers"] + } + enterprise.xcconfig = { 'HEADER_SEARCH_PATHS' => '${SRCROOT}/../SDK/TXLiteAVSDK_Enterprise.framework/Headers/'} + enterprise.source_files = 'Source/*.{h,m,mm}', 'Source/localized/**/*.{h,m,mm}', 'Source/Model/**/*.{h,m,mm}', 'Source/Service/**/*.{h,m,mm}', 'Source/UI/**/*.{h,m,mm}', 'Source/TUICallingKit_Enterprise/*.{h,m,mm}' + enterprise.ios.framework = ['AVFoundation', 'Accelerate', 'AssetsLibrary'] + enterprise.library = 'c++', 'resolv', 'sqlite3' + enterprise.resource_bundles = { + 'TUICallingKitBundle' => ['Resources/Localized/*', 'Resources/AudioFile', 'Resources/*.xcassets'] + } + end + + spec.subspec 'Professional' do |professional| + professional.dependency 'TXLiteAVSDK_Professional' + framework_path="../../SDK/TXLiteAVSDK_Professional.framework" + professional.pod_target_xcconfig={ + 'HEADER_SEARCH_PATHS'=>["$(PODS_TARGET_SRCROOT)/#{framework_path}/Headers"] + } + professional.xcconfig = { 'HEADER_SEARCH_PATHS' => '${SRCROOT}/../SDK/TXLiteAVSDK_Professional.framework/Headers/'} + professional.source_files = 'Source/*.{h,m,mm}', 'Source/localized/**/*.{h,m,mm}', 'Source/Model/**/*.{h,m,mm}', 'Source/Service/**/*.{h,m,mm}', 'Source/UI/**/*.{h,m,mm}', 'Source/TUICallingKit_Professional/*.{h,m,mm}' + professional.ios.framework = ['AVFoundation', 'Accelerate', 'AssetsLibrary'] + professional.library = 'c++', 'resolv', 'sqlite3' + professional.resource_bundles = { + 'TUICallingKitBundle' => ['Resources/Localized/*', 'Resources/AudioFile', 'Resources/*.xcassets'] + } + end + +end + diff --git a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingGroupCell.m b/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingGroupCell.m deleted file mode 100644 index 8ade48abb..000000000 --- a/iOS/TUIKit/TUICalling/iOS/Source/UI/Views/TUICallingGroupCell.m +++ /dev/null @@ -1,51 +0,0 @@ -// -// TUICallingGroupCell.m -// TUICalling -// -// Created by noah on 2021/8/24. -// - -#import "TUICallingGroupCell.h" -#import -#import -#import "UIColor+TUIHex.h" - -@interface TUICallingGroupCell () - -/// 用户昵称 -@property (nonatomic, strong) UILabel *titleLabel; - -@end - -@implementation TUICallingGroupCell - -- (void)setModel:(CallUserModel *)model { - _model = model; - self.titleLabel.text = model.name ?: model.userId; - [self.avatarImageView sd_setImageWithURL:[NSURL URLWithString:model.avatar] placeholderImage:[UIColor imageWithColor:[UIColor t_colorWithHexString:@"999999"] andSize:CGSizeMake(1, 1)]]; -} - -- (instancetype)initWithFrame:(CGRect)frame { - if (self = [super initWithFrame:frame]) { - UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectZero]; - imageView.contentMode = UIViewContentModeScaleAspectFill; - [self.contentView addSubview:imageView]; - self.avatarImageView = imageView; - UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero]; - label.textColor = [UIColor t_colorWithHexString:@"#FFFFFF"]; - label.font = [UIFont systemFontOfSize:12]; - label.textAlignment = NSTextAlignmentLeft; - [self.contentView addSubview:label]; - self.titleLabel = label; - [imageView mas_makeConstraints:^(MASConstraintMaker *make) { - make.edges.equalTo(self.contentView); - }]; - [label mas_makeConstraints:^(MASConstraintMaker *make) { - make.left.equalTo(self.contentView).offset(10); - make.right.bottom.equalTo(self.contentView).offset(-10); - }]; - } - return self; -} - -@end diff --git a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCalling+Signal.h b/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCalling+Signal.h deleted file mode 100644 index 880144fd2..000000000 --- a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCalling+Signal.h +++ /dev/null @@ -1,61 +0,0 @@ -// -// TRTCCall+Signal.h -// TXIMSDK_TUIKit_iOS -// -// Created by xiangzhang on 2020/7/3. -// - -#import "TRTCCalling.h" - -@protocol TRTCCallingDelegate; -@interface TRTCCalling (Signal) - -///添加信令监听 -- (void)addSignalListener; - -///移除信令监听 -- (void)removeSignalListener; - -///通过信令发起通话邀请 -- (NSString *)invite:(NSString *)receiver action:(CallAction)action model:(CallModel *)model cmdInfo:(NSString *)cmdInfo; -- (NSString *)invite:(NSString *)receiver action:(CallAction)action model:(CallModel *)model cmdInfo:(NSString *)cmdInfo userIds:(NSArray *)userIds; - -///收到通话邀请推送通知 -- (void)onReceiveGroupCallAPNs:(V2TIMSignalingInfo *)signalingInfo; - -///检查是否满足自动挂断逻辑 -- (void)checkAutoHangUp; -@end - - -/// TRTCCalling扩展参数 -@interface TRTCCalling () - -@property(nonatomic,copy) NSString *curCallID; -@property(nonatomic,strong) NSMutableArray *curInvitingList; -@property(nonatomic,strong) NSMutableArray *curRoomList; -@property(nonatomic,strong) NSMutableArray *curRespList; -@property(nonatomic,strong) CallModel *curLastModel; -@property(nonatomic,copy) NSString *curGroupID; //群邀请的群 ID -@property(nonatomic,copy) NSString *curSponsorForMe; //对自己发起通话邀请的人 -@property(nonatomic,assign) CallType curType; -@property(nonatomic,assign) UInt32 curRoomID; -@property(nonatomic,assign) BOOL isInRoom; -@property(nonatomic,assign) BOOL isOnCalling; -@property(nonatomic,assign) BOOL isFrontCamera; -@property(nonatomic,assign) UInt64 startCallTS; -@property(nonatomic,strong) NSString *callID; -@property(nonatomic,copy) NSString *switchToAudioCallID; -@property(nonatomic,copy) NSString *currentCallingUserID; - -/// 音视频邀请都需要展示消息,这个参数最好做成可配置,如果设置为 false 信令邀请就会产生 IM 消息 -@property(nonatomic,assign) BOOL onlineUserOnly; - -@property(nonatomic,weak) id delegate; - -- (void)enterRoom; - -- (void)quitRoom; - -@end - diff --git a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCalling+Signal.m b/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCalling+Signal.m deleted file mode 100644 index 04c504313..000000000 --- a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCalling+Signal.m +++ /dev/null @@ -1,608 +0,0 @@ -// -// TRTCCall+Signal.m -// TXIMSDK_TUIKit_iOS -// -// Created by xiangzhang on 2020/7/3. -// - -#import "TRTCCalling+Signal.h" -#import "TRTCCallingUtils.h" -#import "TRTCCallingHeader.h" -#import -#import "CallingLocalized.h" -#import "TRTCSignalFactory.h" - -@implementation TRTCCalling (Signal) - -- (void)addSignalListener { - [[V2TIMManager sharedInstance] addSignalingListener:self]; -} - -- (void)removeSignalListener { - [[V2TIMManager sharedInstance] removeSignalingListener:self]; -} - -- (NSString *)invite:(NSString *)receiver action:(CallAction)action model:(CallModel *)model cmdInfo:(NSString *)cmdInfo { - return [self invite:receiver action:action model:model cmdInfo:cmdInfo userIds:nil]; -} - -- (NSString *)invite:(NSString *)receiver action:(CallAction)action model:(CallModel *)model cmdInfo:(NSString *)cmdInfo userIds:(NSArray *)userIds { - NSString *callID = @""; - - - if (receiver.length <= 0) { - return callID; - } - CallModel *realModel = [self generateModel:action]; - BOOL isGroup = [self.curGroupID isEqualToString:receiver]; - if (model) { - realModel = [model copy]; - realModel.action = action; - if (model.groupid.length > 0) { - isGroup = YES; - } - } - switch (realModel.action) { - case CallAction_Call: - { - NSString *cmd = @""; - switch (realModel.calltype) { - case CallType_Video: - cmd = SIGNALING_CMD_VIDEOCALL; - break; - - case CallType_Audio: - cmd = SIGNALING_CMD_AUDIOCALL; - break; - default: - break; - } - NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:cmd cmdInfo:@"" userIds:userIds ?: @[] message:@"" callType:realModel.calltype]; - [dataDic setValue:@(realModel.roomid) forKey:SIGNALING_EXTRA_KEY_ROOM_ID]; - NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; - - if (isGroup) { - @weakify(self) - callID = [[V2TIMManager sharedInstance] inviteInGroup:realModel.groupid inviteeList:realModel.invitedList data:data onlineUserOnly:self.onlineUserOnly timeout:SIGNALING_EXTRA_KEY_TIME_OUT succ:^{ - @strongify(self) - // 发起 Apns 推送,群组的邀请,需要单独对每个被邀请人发起推送 - for (NSString *invitee in realModel.invitedList) { - [self sendAPNsForCall:invitee inviteeList:realModel.invitedList callID:self.callID groupid:realModel.groupid roomid:realModel.roomid]; - } - } fail:^(int code, NSString *desc) { - @strongify(self) - if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { - [self.delegate onError:code msg:desc]; - }; - }]; - self.callID = callID; - } else { - @weakify(self) - V2TIMOfflinePushInfo *info = [self getOfflinePushInfoWithInviteeList:realModel.invitedList callID:nil groupid:nil roomid:realModel.roomid]; - callID = [[V2TIMManager sharedInstance] invite:receiver data:data onlineUserOnly:self.onlineUserOnly offlinePushInfo:info timeout:SIGNALING_EXTRA_KEY_TIME_OUT succ:^{ - } fail:^(int code, NSString *desc) { - @strongify(self) - if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { - [self.delegate onError:code msg:desc]; - }; - }]; - self.callID = callID; - } - } - break; - - case CallAction_Accept: - { - NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:@"" cmdInfo:@"" message:@"" callType:realModel.calltype]; - NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; - @weakify(self) - [[V2TIMManager sharedInstance] accept:realModel.callid data:data succ:nil fail:^(int code, NSString *desc) { - @strongify(self) - if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { - [self.delegate onError:code msg:desc]; - }; - }]; - } - break; - - case CallAction_Reject: - { - NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:@"" cmdInfo:@"" message:@"" callType:realModel.calltype]; - NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; - @weakify(self) - [[V2TIMManager sharedInstance] reject:realModel.callid data:data succ:nil fail:^(int code, NSString *desc) { - @strongify(self) - if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { - [self.delegate onError:code msg:desc]; - }; - }]; - } - break; - - case CallAction_Linebusy: - { - NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:@"" cmdInfo:@"" message:SIGNALING_MESSAGE_LINEBUSY callType:realModel.calltype]; - [dataDic setValue:SIGNALING_EXTRA_KEY_LINE_BUSY forKey:SIGNALING_EXTRA_KEY_LINE_BUSY]; - NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; - @weakify(self) - [[V2TIMManager sharedInstance] reject:realModel.callid data:data succ:nil fail:^(int code, NSString *desc) { - @strongify(self) - if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { - [self.delegate onError:code msg:desc]; - }; - }]; - } - break; - - case CallAction_Cancel: - { - NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:@"" cmdInfo:@"" message:@"" callType:realModel.calltype]; - NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; - @weakify(self) - [[V2TIMManager sharedInstance] cancel:realModel.callid data:data succ:nil fail:^(int code, NSString *desc) { - @strongify(self) - if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { - [self.delegate onError:code msg:desc]; - }; - }]; - } - break; - - case CallAction_End: - { - if (isGroup) { - // 群通话不需要计算通话时长 - NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:SIGNALING_CMD_HANGUP cmdInfo:@"0" message:@"" callType:realModel.calltype]; - [dataDic setValue:@(0) forKey:SIGNALING_EXTRA_KEY_CALL_END]; - NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; - // 这里发结束事件的时候,inviteeList 已经为 nil 了,可以伪造一个被邀请用户,把结束的信令发到群里展示。 - // timeout 这里传 0,结束的事件不需要做超时检测 - @weakify(self) - callID = [[V2TIMManager sharedInstance] inviteInGroup:realModel.groupid inviteeList:@[@"inviteeList"] data:data onlineUserOnly:self.onlineUserOnly timeout:0 succ:nil fail:^(int code, NSString *desc) { - @strongify(self) - if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { - [self.delegate onError:code msg:desc]; - }; - }]; - } else { - NSDate *now = [NSDate date]; - NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:SIGNALING_CMD_HANGUP cmdInfo:[NSString stringWithFormat:@"%llu",(UInt64)[now timeIntervalSince1970] - self.startCallTS] message:@"" callType:realModel.calltype]; - [dataDic setValue:@((UInt64)[now timeIntervalSince1970] - self.startCallTS) forKey:SIGNALING_EXTRA_KEY_CALL_END]; - NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; - @weakify(self) - callID = [[V2TIMManager sharedInstance] invite:receiver data:data onlineUserOnly:self.onlineUserOnly offlinePushInfo:nil timeout:0 succ:nil fail:^(int code, NSString *desc) { - @strongify(self) - if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { - [self.delegate onError:code msg:desc]; - }; - }]; - self.startCallTS = 0; - } - } - break; - case CallAction_SwitchToAudio: { - NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:SIGNALING_CMD_SWITCHTOVOICECALL cmdInfo:@"" message:@"" callType:realModel.calltype]; - [dataDic setValue:SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL forKey:SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL]; - NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; - @weakify(self) - [[V2TIMManager sharedInstance] invite:receiver data:data onlineUserOnly:self.onlineUserOnly offlinePushInfo:nil timeout:SIGNALING_EXTRA_KEY_TIME_OUT succ:^{ - - } fail:^(int code, NSString *desc) { - @strongify(self) - if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { - [self.delegate onError:code msg:desc]; - }; - }]; - } break; - - default: - break; - } - if (realModel.action != CallAction_Reject && - realModel.action != CallAction_Accept && - realModel.action != CallAction_End && - realModel.action != CallAction_Cancel && - realModel.action != CallAction_SwitchToAudio && - realModel.action != CallAction_AcceptSwitchToAudio && - realModel.action != CallAction_RejectSwitchToAudio && - model == nil) { - self.curLastModel = [realModel copy]; - } - return callID; -} - -- (void)sendAPNsForCall:(NSString *)receiver inviteeList:(NSArray *)inviteeList callID:(NSString *)callID groupid:(NSString *)groupid roomid:(UInt32)roomid { - if (callID.length == 0 || inviteeList.count == 0 || roomid == 0) { - NSLog(@"sendAPNsForCall failed"); - return; - } - V2TIMOfflinePushInfo *info = [self getOfflinePushInfoWithInviteeList:inviteeList callID:callID groupid:groupid roomid:roomid]; - V2TIMMessage *msg = [[V2TIMManager sharedInstance] createCustomMessage:[TRTCCallingUtils dictionary2JsonData:@{@"version" : @(Version) , @"businessID" : SIGNALING_BUSINESSID}]]; - // 针对每个被邀请成员单独邀请 - [[V2TIMManager sharedInstance] sendMessage:msg receiver:receiver groupID:nil priority:V2TIM_PRIORITY_HIGH onlineUserOnly:YES offlinePushInfo:info progress:nil succ:nil fail:nil]; -} - - -- (V2TIMOfflinePushInfo *)getOfflinePushInfoWithInviteeList:(NSArray *)inviteeList callID:(NSString *)callID groupid:(NSString *)groupid roomid:(UInt32)roomid{ - int chatType; //单聊:1 群聊:2 - if (groupid.length > 0) { - chatType = 2; - } else { - chatType = 1; - groupid = @""; - } - //{"entity":{"version":1,"content":"{\"action\":1,\"call_type\":2,\"room_id\":804544637,\"call_id\":\"144115224095613335-1595234230-3304653590\",\"timeout\":30,\"version\":4,\"invited_list\":[\"2019\"],\"group_id\":\"@TGS#1PWYXLTGA\"}","sendTime":1595234231,"sender":"10457","chatType":2,"action":2}} - NSDictionary *contentParam = @{@"action":@(SignalingActionType_Invite), - @"call_id":callID ?: @"", - @"call_type":@(self.curType), - @"invited_list":inviteeList.count > 0 ? inviteeList : @[], - @"room_id":@(roomid), - @"group_id":groupid, - @"timeout":@(SIGNALING_EXTRA_KEY_TIME_OUT), - @"version":@(Version)}; // TUIkit 业务版本 - NSDictionary *entityParam = @{@"action" : @(APNs_Business_Call), // 音视频业务逻辑推送 - @"chatType" : @(chatType), - @"content" : [TRTCCallingUtils dictionary2JsonStr:contentParam], - @"sendTime" : @((UInt32)[[V2TIMManager sharedInstance] getServerTime]), - @"sender" : [TRTCCallingUtils loginUser], - @"version" : @(APNs_Version)}; // 推送版本 - NSDictionary *extParam = @{@"entity" : entityParam}; - V2TIMOfflinePushInfo *info = [[V2TIMOfflinePushInfo alloc] init]; - info.desc = CallingLocalize(@"Demo.TRTC.calling.callingrequest"); - info.ext = [TRTCCallingUtils dictionary2JsonStr:extParam]; - return info; -} - -- (void)onReceiveGroupCallAPNs:(V2TIMSignalingInfo *)signalingInfo { - if (signalingInfo.inviteID.length > 0 && signalingInfo.inviter.length > 0 && signalingInfo.inviteeList.count > 0 && signalingInfo.groupID.length > 0) { - [[V2TIMManager sharedInstance] addInvitedSignaling:signalingInfo succ:^{ - [self onReceiveNewInvitation:signalingInfo.inviteID inviter:signalingInfo.inviter groupID:signalingInfo.groupID inviteeList:signalingInfo.inviteeList data:signalingInfo.data]; - } fail:^(int code, NSString *desc) { - NSLog(@"onReceiveAPNsForGroupCall failed,code:%d desc:%@",code,desc); - }]; - } -} - -#pragma mark - V2TIMSignalingListener - --(void)onReceiveNewInvitation:(NSString *)inviteID inviter:(NSString *)inviter groupID:(NSString *)groupID inviteeList:(NSArray *)inviteeList data:(NSString *)data { - NSDictionary *param = [self check:data]; - if (param) { - NSString *cmdInfoStr = @""; - NSArray *userIds = nil; - - if ([param.allKeys containsObject:SIGNALING_EXTRA_KEY_DATA]) { - NSDictionary *data = param[SIGNALING_EXTRA_KEY_DATA]; - if ([data.allKeys containsObject:SIGNALING_EXTRA_KEY_CMDINFO]) { - cmdInfoStr = data[SIGNALING_EXTRA_KEY_CMDINFO]; - } - if ([data.allKeys containsObject:SIGNALING_EXTRA_KEY_USERIDS]) { - userIds = data[SIGNALING_EXTRA_KEY_USERIDS]; - } - } - - NSDictionary *data = [TRTCSignalFactory getDataDictionary:param]; - CallModel *model = [[CallModel alloc] init]; - model.callid = inviteID; - model.groupid = groupID; - model.inviter = inviter; - model.invitedList = [NSMutableArray arrayWithArray:inviteeList]; - model.calltype = [TRTCSignalFactory convertCmdToCallType:data[SIGNALING_EXTRA_KEY_CMD]]; - model.roomid = [data[SIGNALING_EXTRA_KEY_ROOMID] intValue]; - if ([data[SIGNALING_EXTRA_KEY_CMD] isEqualToString:SIGNALING_CMD_SWITCHTOVOICECALL]) { - model.action = CallAction_SwitchToAudio; - self.switchToAudioCallID = inviteID; - } - else { - self.currentCallingUserID = inviter; - model.action = CallAction_Call; - } - [self handleCallModel:inviter model:model message:cmdInfoStr userIds:userIds]; - } -} - --(void)onInvitationCancelled:(NSString *)inviteID inviter:(NSString *)inviter data:(NSString *)data { - NSDictionary *param = [self check:data]; - if (param) { - CallModel *model = [[CallModel alloc] init]; - model.callid = inviteID; - model.action = CallAction_Cancel; - [self handleCallModel:inviter model:model message:@""]; - } -} - --(void)onInviteeAccepted:(NSString *)inviteID invitee:(NSString *)invitee data:(NSString *)data { - NSDictionary *param = [self check:data]; - if (param) { - [TRTCCloud sharedInstance].delegate = self; - CallModel *model = [[CallModel alloc] init]; - model.callid = inviteID; - NSDictionary *data = [TRTCSignalFactory getDataDictionary:param]; - if ([data[SIGNALING_EXTRA_KEY_CMD] isEqualToString:SIGNALING_CMD_SWITCHTOVOICECALL]) { - model.action = CallAction_AcceptSwitchToAudio; - } - else { - model.action = CallAction_Accept; - } - [self handleCallModel:invitee model:model message:@""]; - } -} - --(void)onInviteeRejected:(NSString *)inviteID invitee:(NSString *)invitee data:(NSString *)data { - NSDictionary *param = [self check:data]; - if (param) { - CallModel *model = [[CallModel alloc] init]; - model.callid = inviteID; - NSDictionary *data = [TRTCSignalFactory getDataDictionary:param]; - if ([data[SIGNALING_EXTRA_KEY_MESSAGE] isEqualToString:SIGNALING_MESSAGE_LINEBUSY]) { - model.action = CallAction_Linebusy; - } - else if ([data[SIGNALING_EXTRA_KEY_CMD] isEqualToString:SIGNALING_CMD_SWITCHTOVOICECALL]) { - model.action = CallAction_AcceptSwitchToAudio; - } - else { - model.action = CallAction_Reject; - } - [self handleCallModel:invitee model:model message:@"Other status error"]; - } -} - --(void)onInvitationTimeout:(NSString *)inviteID inviteeList:(NSArray *)invitedList { - CallModel *model = [[CallModel alloc] init]; - model.callid = inviteID; - model.invitedList = [NSMutableArray arrayWithArray:invitedList]; - model.action = CallAction_Timeout; - [self handleCallModel:@"" model:model message:@"Timeout"]; -} - -- (NSDictionary *)check:(NSString *)data { - NSDictionary *signalingDictionary = [TRTCCallingUtils jsonSring2Dictionary:data]; - - if(!signalingDictionary[SIGNALING_EXTRA_KEY_PLATFORM] || !signalingDictionary[SIGNALING_EXTRA_KEY_DATA]) { - signalingDictionary = [TRTCSignalFactory convertOldSignalingToNewSignaling:signalingDictionary]; - } - - if (![signalingDictionary[SIGNALING_EXTRA_KEY_BUSINESSID] isEqualToString:SIGNALING_BUSINESSID]) { - return nil; - } - NSInteger version = [signalingDictionary[SIGNALING_EXTRA_KEY_VERSION] integerValue]; - if (version > Version) { - return nil; - } - NSDictionary *dataDictionary = [TRTCSignalFactory getDataDictionary:signalingDictionary]; - if ([dataDictionary[SIGNALING_EXTRA_KEY_CMD] isEqualToString:SIGNALING_CMD_HANGUP]) { - //结束的事件只用于 UI 展示通话时长,不参与业务逻辑的处理 - return nil; - } - return signalingDictionary; -} - -- (void)handleCallModel:(NSString *)user model:(CallModel *)model message:(NSString *)message { - [self handleCallModel:user model:model message:message userIds:nil]; -} - -- (void)handleCallModel:(NSString *)user model:(CallModel *)model message:(NSString *)message userIds:(NSArray *)userIds { - switch (model.action) { - case CallAction_Call: - { - void(^syncInvitingList)(void) = ^(){ - if (self.curGroupID.length > 0) { - for (NSString *invitee in model.invitedList) { - if (![self.curInvitingList containsObject:invitee]) { - [self.curInvitingList addObject:invitee]; - } - } - } - }; - if (model.groupid != nil && ![model.invitedList containsObject:[TRTCCallingUtils loginUser]] - ) { //群聊但是邀请不包含自己不处理 - if (self.curCallID == model.callid) { //在房间中更新列表 - syncInvitingList(); - if (self.curInvitingList.count > 0) { - if ([self canDelegateRespondMethod:@selector(onGroupCallInviteeListUpdate:)]) { - [self.delegate onGroupCallInviteeListUpdate:self.curInvitingList]; - } - } - } - return; - } - if (self.isOnCalling) { // tell busy - if (![model.callid isEqualToString:self.curCallID]) { - [self invite:model.groupid.length > 0 ? model.groupid : user action:CallAction_Linebusy model:model cmdInfo:nil]; - } - } else { - self.isOnCalling = true; - self.curCallID = model.callid; - self.curRoomID = model.roomid; - if (model.groupid.length > 0) { - self.curGroupID = model.groupid; - } - self.curType = model.calltype; - self.curSponsorForMe = user; - syncInvitingList(); - if ([self canDelegateRespondMethod:@selector(onInvited:userIds:isFromGroup:callType:)]) { - NSMutableArray *userIdAry = model.invitedList; - [userIds enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - if (![userIdAry containsObject:obj]) { - [userIdAry addObject:obj]; - } - }]; - - [self.delegate onInvited:user userIds:userIdAry isFromGroup:self.curGroupID.length > 0 ? YES : NO callType:model.calltype]; - } - } - - } - break; - - case CallAction_Cancel: - { - if ([self.curCallID isEqualToString:model.callid] && self.delegate) { - [self checkAutoHangUp]; - self.isOnCalling = NO; - [self.delegate onCallingCancel:user]; - } - } - break; - - case CallAction_Reject: - { - if ([self.curCallID isEqualToString:model.callid] && self.delegate) { - if ([self.curInvitingList containsObject:user]) { - [self.curInvitingList removeObject:user]; - } - if ([self canDelegateRespondMethod:@selector(onReject:)]) { - [self.delegate onReject:user]; - } - [self checkAutoHangUp]; - } - } - break; - - case CallAction_Timeout: - if ([self.curCallID isEqualToString:model.callid] && self.delegate) { - // 这里需要判断下是否是自己超时了,自己超时,直接退出界面 - if ([model.invitedList containsObject:[TRTCCallingUtils loginUser]] && self.delegate) { - self.isOnCalling = false; - if ([self canDelegateRespondMethod:@selector(onCallingTimeOut)]) { - [self.delegate onCallingTimeOut]; - } - } else { - for (NSString *userID in model.invitedList) { - if ([self.curInvitingList containsObject:userID] && ![self.curRespList containsObject:userID]) { - if ([self canDelegateRespondMethod:@selector(onNoResp:)]) { - [self.delegate onNoResp:userID]; - } - if ([self.curInvitingList containsObject:userID]) { - [self.curInvitingList removeObject:userID]; - } - } - } - } - [self checkAutoHangUp]; - } - break; - - case CallAction_End: - // 不需处理 - break; - - case CallAction_Linebusy: - { - if ([self.curCallID isEqualToString:model.callid] && self.delegate) { - if ([self.curInvitingList containsObject:user]) { - [self.curInvitingList removeObject:user]; - } - [self.delegate onLineBusy:user]; - [self checkAutoHangUp]; - } - } - break; - - case CallAction_Error: - if ([self.curCallID isEqualToString:model.callid] && self.delegate) { - if ([self.curInvitingList containsObject:user]) { - [self.curInvitingList removeObject:user]; - } - [self.delegate onError:-1 msg:CallingLocalize(@"Demo.TRTC.calling.syserror")]; - [self checkAutoHangUp]; - } - break; - case CallAction_SwitchToAudio: { - if (!self.switchToAudioCallID) { - break; - } - int res = [self checkAudioStatus]; - if (res == 0) { - NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:0 cmd:SIGNALING_CMD_SWITCHTOVOICECALL cmdInfo:@"" message:@"" callType:CallType_Video]; - [dataDic setValue:@(1) forKey:SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL]; - NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; - @weakify(self) - [[V2TIMManager sharedInstance] accept:self.switchToAudioCallID data:data succ:^{ - @strongify(self) - if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { - [self.delegate onSwitchToAudio:YES message:@""]; - } - } fail:^(int code, NSString *desc) { - @strongify(self) - if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { - [self.delegate onSwitchToAudio:NO message:desc]; - } - }]; - } - else { - NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:0 cmd:@"" cmdInfo:@"" message:@"" callType:CallType_Video]; - [dataDic setValue:@(0) forKey:SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL]; - NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; - [[V2TIMManager sharedInstance] reject:self.switchToAudioCallID data:data succ:^{ - - } fail:^(int code, NSString *desc) { - - }]; - if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { - [self.delegate onSwitchToAudio:NO message:@"Local status error"]; - } - } - } break; - case CallAction_AcceptSwitchToAudio: { - if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { - [self.delegate onSwitchToAudio:YES message:@""]; - } - } break; - case CallAction_RejectSwitchToAudio: { - if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { - [self.delegate onSwitchToAudio:NO message:message]; - } - } break; - default: - { - NSLog(@"📳 👻 WTF ????"); - } - break; - } -} - -#pragma mark - utils - -- (CallModel *)generateModel:(CallAction)action { - CallModel *model = [self.curLastModel copy]; - model.action = action; - return model; -} - -// 检查是否能自动挂断 -- (void)checkAutoHangUp { - if (self.isInRoom && self.curRoomList.count == 0) { - if (self.curGroupID.length > 0) { - if (self.curInvitingList.count == 0) { - [self invite:self.curGroupID action:CallAction_End model:nil cmdInfo:nil]; - [self autoHangUp]; - } - } else { - NSString *user = @""; - if (self.curSponsorForMe.length > 0) { - user = self.curSponsorForMe; - } else { - user = self.curLastModel.invitedList.firstObject; - } - [self invite:user action:CallAction_End model:nil cmdInfo:nil]; - [self autoHangUp]; - } - } -} - -// 自动挂断 -- (void)autoHangUp { - [self quitRoom]; - self.isOnCalling = NO; - if ([self canDelegateRespondMethod:@selector(onCallEnd)]) { - [self.delegate onCallEnd]; - } -} - -#pragma mark - private method -- (BOOL)canDelegateRespondMethod:(SEL)selector { - return self.delegate && [self.delegate respondsToSelector:selector]; -} - -@end diff --git a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCallingHeader.h b/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCallingHeader.h deleted file mode 100644 index 891014d06..000000000 --- a/iOS/TUIKit/TUICalling/iOS/Source/model/Impl/TRTCCallingHeader.h +++ /dev/null @@ -1,66 +0,0 @@ -// -// TRTCCallingHeader.h -// TXLiteAVDemo -// -// Created by abyyxwang on 2020/8/4. -// Copyright © 2020 Tencent. All rights reserved. -// - -#ifndef TRTCCallingHeader_h -#define TRTCCallingHeader_h - -#define Version 4 //TUIkit 业务版本 -#define APNs_Version 1 //推送版本 -#define APNs_Business_NormalMsg 1 //普通消息推送 -#define APNs_Business_Call 2 //音视频通话推送 - -#ifdef DEBUG -#define TRTCLog(fmt, ...) NSLog((@"TRTC LOG:%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) -#else -#define TRTCLog(...) -#endif - -/** - Synthsize a weak or strong reference. - - Example: - @weakify(self) - [self doSomething^{ - @strongify(self) - if (!self) return; - ... - }]; - */ -#ifndef weakify - #if DEBUG - #if __has_feature(objc_arc) - #define weakify(object) autoreleasepool{} __weak __typeof__(object) weak##_##object = object; - #else - #define weakify(object) autoreleasepool{} __block __typeof__(object) block##_##object = object; - #endif - #else - #if __has_feature(objc_arc) - #define weakify(object) try{} @finally{} {} __weak __typeof__(object) weak##_##object = object; - #else - #define weakify(object) try{} @finally{} {} __block __typeof__(object) block##_##object = object; - #endif - #endif -#endif - -#ifndef strongify - #if DEBUG - #if __has_feature(objc_arc) - #define strongify(object) autoreleasepool{} __typeof__(object) object = weak##_##object; - #else - #define strongify(object) autoreleasepool{} __typeof__(object) object = block##_##object; - #endif - #else - #if __has_feature(objc_arc) - #define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object; - #else - #define strongify(object) try{} @finally{} __typeof__(object) object = block##_##object; - #endif - #endif -#endif - -#endif /* TRTCCallingHeader_h */ diff --git a/iOS/TUIKit/TUIChat/Cell/CellUI/Base/TUIInputMoreCell.m b/iOS/TUIKit/TUIChat/Cell/CellUI/Base/TUIInputMoreCell.m index fbb78f1b0..0ea66dcbc 100644 --- a/iOS/TUIKit/TUIChat/Cell/CellUI/Base/TUIInputMoreCell.m +++ b/iOS/TUIKit/TUIChat/Cell/CellUI/Base/TUIInputMoreCell.m @@ -26,7 +26,7 @@ [self addSubview:_image]; _title = [[UILabel alloc] init]; - [_title setFont:[UIFont systemFontOfSize:14]]; + [_title setFont:[UIFont systemFontOfSize:10]]; [_title setTextColor:[UIColor grayColor]]; _title.textAlignment = NSTextAlignmentCenter; [self addSubview:_title]; diff --git a/iOS/TUIKit/TUIChat/Cell/CellUI/Chat/TUIVideoCollectionCell.m b/iOS/TUIKit/TUIChat/Cell/CellUI/Chat/TUIVideoCollectionCell.m index 97abcf223..dcde2755c 100644 --- a/iOS/TUIKit/TUIChat/Cell/CellUI/Chat/TUIVideoCollectionCell.m +++ b/iOS/TUIKit/TUIChat/Cell/CellUI/Chat/TUIVideoCollectionCell.m @@ -135,6 +135,11 @@ if (self.isSaveVideo) { [self saveVideo]; } + + // 如果还没播放,或者播放错误,将在线地址切成本地 + if (self.player.status == AVPlayerStatusFailed || self.player.status == AVPlayerStatusReadyToPlay) { + [self addPlayer:[NSURL fileURLWithPath:self.videoPath]]; + } }]; } diff --git a/iOS/TUIKit/TUIChat/DataProvider/TUIMessageDataProvider.m b/iOS/TUIKit/TUIChat/DataProvider/TUIMessageDataProvider.m index 022cdaed4..260c40a94 100644 --- a/iOS/TUIKit/TUIChat/DataProvider/TUIMessageDataProvider.m +++ b/iOS/TUIKit/TUIChat/DataProvider/TUIMessageDataProvider.m @@ -829,6 +829,7 @@ static NSArray *customMessageInfo = nil; NSData *data = [NSJSONSerialization dataWithJSONObject:ext options:NSJSONWritingPrettyPrinted error:nil]; pushInfo.ext = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; pushInfo.AndroidOPPOChannelID = @"tuikit"; + pushInfo.AndroidSound = TUIConfig.defaultConfig.enableCustomRing ? @"private_ring" : nil; } return [V2TIMManager.sharedInstance sendMessage:message diff --git a/iOS/TUIKit/TUIChat/Private Center/TUIUserAuthorizationCenter.h b/iOS/TUIKit/TUIChat/Private Center/TUIUserAuthorizationCenter.h new file mode 100644 index 000000000..1c7efb5a2 --- /dev/null +++ b/iOS/TUIKit/TUIChat/Private Center/TUIUserAuthorizationCenter.h @@ -0,0 +1,37 @@ +// +// TUIUserAuthorizationCenter.h +// TUIChat +// +// Created by wyl on 2022/2/16. +// + +#import +#import + + +typedef NS_OPTIONS(NSUInteger, TUIChatAuthControlType) { + TUIChatAuthControlTypeMicro = 1 << 0, + TUIChatAuthControlTypeCamera = 1 << 1, + TUIChatAuthControlTypePhoto = 1 << 2, +}; +@interface TUIUserAuthorizationCenter : NSObject +@property (nonatomic, assign, class, readonly) BOOL isEnablePushAuthotization; +@property (nonatomic, assign, class, readonly) BOOL isEnableCameraAuthorization; +@property (nonatomic, assign, class, readonly) BOOL isEnablePhotoAuthorization; +@property (nonatomic, assign, class, readonly) BOOL isEnableAudioRecognizeAuthorization; +@property (nonatomic, assign, class, readonly) BOOL isEnableMicroAuthorization; +@property (nonatomic, assign, class, readonly) BOOL isEnableLocationAuthorization; + + ++ (void)userIsAllowPush:(void (^)(BOOL))isOpenPushBlock API_AVAILABLE(ios(8.0)); ++ (BOOL)isEnablePushAuthotization:(void (^) (BOOL enable))completion API_AVAILABLE(ios(8.0)); ++ (void)cameraStateActionWithPopCompletion:(void (^)(void))completion API_AVAILABLE(ios(8.0)); ++ (void)photoStateActionWithPopCompletion:(void (^)(void))completion API_AVAILABLE(ios(8.0)); ++ (void)microStateActionWithPopCompletion:(void (^)(void))completion API_AVAILABLE(ios(8.0)); ++ (void)audioRecogStateActionWithPopCompletion:(void (^)(void))completion API_AVAILABLE(ios(8.0)); ++ (void)locationStateActionWithPopCompletion:(void (^)(void))completion API_AVAILABLE(ios(8.0)); + ++ (void)openSettingPage; ++ (void)showAlert:(TUIChatAuthControlType)type; +@end + diff --git a/iOS/TUIKit/TUIChat/Private Center/TUIUserAuthorizationCenter.m b/iOS/TUIKit/TUIChat/Private Center/TUIUserAuthorizationCenter.m new file mode 100644 index 000000000..d31de014e --- /dev/null +++ b/iOS/TUIKit/TUIChat/Private Center/TUIUserAuthorizationCenter.m @@ -0,0 +1,269 @@ +// +// TUIUserAuthorizationCenter.m +// TUIChat +// +// Created by wyl on 2022/2/16. +// + +#import "TUIUserAuthorizationCenter.h" +#import +#import +#import +#import +#import +#import +#import +#import "TUIDefine.h" + +#import "TUIGlobalization.h" +@implementation TUIUserAuthorizationCenter + ++ (void)userIsAllowPush:(void (^)(BOOL))isOpenPushBlock { + if (@available(iOS 10.0, *)) { + [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { + dispatch_async(dispatch_get_main_queue(), ^{ + BOOL isAllow = (settings.authorizationStatus == UNAuthorizationStatusAuthorized); + + if (isOpenPushBlock) { + isOpenPushBlock(isAllow); + } + }); + + }]; + }else{ + dispatch_async(dispatch_get_main_queue(), ^{ + UIUserNotificationSettings *settings = [[UIApplication sharedApplication] currentUserNotificationSettings]; + BOOL isAllow = (settings.types != UIUserNotificationTypeNone); + if (isOpenPushBlock) { + isOpenPushBlock(isAllow); + } + }); + } + +} ++ (BOOL)isEnablePushAuthotization:(void (^) (BOOL enable))completion { + + if (@available(iOS 10.0, *)) { + + __block BOOL enable = NO; + [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) { + enable = settings.authorizationStatus != UNAuthorizationStatusNotDetermined && settings.authorizationStatus != UNAuthorizationStatusDenied; + + if (completion){ + completion(enable); + } + }]; + return enable; + } else { + + if (@available(iOS 8.0, *)) { + return [[UIApplication sharedApplication] currentUserNotificationSettings].types != UIUserNotificationTypeNone; + } else { + // Fallback on earlier versions + } + } + + return NO; +} + ++ (BOOL)isEnableCameraAuthorization { + + if (@available(iOS 7.0, *)) { + return [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] == AVAuthorizationStatusAuthorized; + } else { + return YES; + } +} + ++ (void)cameraStateActionWithPopCompletion:(void (^)(void))completion { + if ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] == AVAuthorizationStatusNotDetermined) { + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { + if (granted && completion) { + completion(); + } + }]; + } else { + [self showAlert:TUIChatAuthControlTypeCamera]; + } +} + ++ (void)openSettingPage { + if (@available(iOS 8.0, *)) { + NSURL * url = [NSURL URLWithString:UIApplicationOpenSettingsURLString]; + if ([[UIApplication sharedApplication] canOpenURL:url]) { + + if (@available(iOS 10.0, *)) { + + [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; + } else { + + [[UIApplication sharedApplication] openURL:url]; + } + } + } else { + // Fallback on earlier versions + } +} + ++ (BOOL)isEnablePhotoAuthorization { + + if (@available(iOS 8, *)) { + return [PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized; + } else { + return YES; + } +} + ++ (void)photoStateActionWithPopCompletion:(void (^)(void))completion { + PHAuthorizationStatus authStatus = [PHPhotoLibrary authorizationStatus]; + if (authStatus == PHAuthorizationStatusNotDetermined) { + [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { + if (status == PHAuthorizationStatusAuthorized && completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(); + }); + } + }]; + } else if (authStatus == PHAuthorizationStatusAuthorized && completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(); + }); + } else { + [self showAlert:TUIChatAuthControlTypePhoto]; + } +} + ++ (BOOL)isEnableMicroAuthorization { + + if (@available(iOS 7.0, *)) { + return [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio] == AVAuthorizationStatusAuthorized; + } else { + return YES; + } +} + ++ (void)microStateActionWithPopCompletion:(void (^)(void))completion { +#if !TARGET_OS_MACCATALYST + if ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio] == AVAuthorizationStatusNotDetermined) { + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) { + if (granted && completion) { + completion(); + } + }]; + } else { + [self showAlert:TUIChatAuthControlTypeMicro]; + } +#endif +} + + ++ (BOOL)isEnableLocationAuthorization { + CLAuthorizationStatus status = [CLLocationManager authorizationStatus]; + if (@available(iOS 8.0, *)) { + return status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse; + } else { + // Fallback on earlier versions + return YES; + } +} + ++ (void)locationStateActionWithPopCompletion:(void (^)(void))completion { + [self openSettingPage]; +} + + ++ (BOOL)isEnableAudioRecognizeAuthorization { + + if (@available(iOS 10.0, *)) { + return [SFSpeechRecognizer authorizationStatus] == SFSpeechRecognizerAuthorizationStatusAuthorized; + } + + return NO; +} + ++ (void)audioRecogStateActionWithPopCompletion:(void (^)(void))completion { + if (@available(iOS 10.0, *)) { + if ([SFSpeechRecognizer authorizationStatus] == SFSpeechRecognizerAuthorizationStatusNotDetermined) { + [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) { + if (status == SFSpeechRecognizerAuthorizationStatusAuthorized && completion) { + completion(); + } + }]; + } else { + [self openSettingPage]; + + } + } +} + + ++ (BOOL)isEnableCalendarAuthorization { + EKAuthorizationStatus authorizationStatus = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent]; + if(authorizationStatus != EKAuthorizationStatusAuthorized){ + return NO; + } else { + return YES; + } +} ++ (BOOL)isEnableSportsAndFitnessAuthorization { + BOOL isAuth = NO; + if (@available(iOS 14.0, *)) { + CMHeadphoneMotionManager *motionMgr = [[CMHeadphoneMotionManager alloc] init]; + if (motionMgr.isDeviceMotionAvailable) { + isAuth = [CMHeadphoneMotionManager authorizationStatus] == CMAuthorizationStatusAuthorized; + } + } + return isAuth; +} + + ++ (void)showAlert:(TUIChatAuthControlType)type { + + NSString * title = @""; + NSString * message = @""; + NSString * laterMessage = @""; + NSString * openSettingMessage = @""; + + if (TUIChatAuthControlTypeMicro == type) { + title = TUIKitLocalizableString(TUIKitInputNoMicTitle); + message = TUIKitLocalizableString(TUIKitInputNoMicTips); + laterMessage = TUIKitLocalizableString(TUIKitInputNoMicOperateLater); + openSettingMessage = TUIKitLocalizableString(TUIKitInputNoMicOperateEnable); + } + else if (TUIChatAuthControlTypeCamera == type){ + title = TUIKitLocalizableString(TUIKitInputNoCameraTitle); + message = TUIKitLocalizableString(TUIKitInputNoCameraTips); + laterMessage = TUIKitLocalizableString(TUIKitInputNoCameraOperateLater); + openSettingMessage = TUIKitLocalizableString(TUIKitInputNoCameraOperateEnable); + } + else if (TUIChatAuthControlTypePhoto == type) { + title = TUIKitLocalizableString(TUIKitInputNoPhotoTitle); + message = TUIKitLocalizableString(TUIKitInputNoPhotoTips); + laterMessage = TUIKitLocalizableString(TUIKitInputNoPhotoOperateLater); + openSettingMessage = TUIKitLocalizableString(TUIKitInputNoPhotoerateEnable); + } + else { + return; + } + if (@available(iOS 8.0, *)) { + UIAlertController *ac = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; + [ac addAction:[UIAlertAction actionWithTitle:laterMessage style:UIAlertActionStyleCancel handler:nil]]; + [ac addAction:[UIAlertAction actionWithTitle:openSettingMessage style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIApplication *app = [UIApplication sharedApplication]; + NSURL *settingsURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString]; + if ([app canOpenURL:settingsURL]) { + [app openURL:settingsURL]; + } + }]]; + dispatch_async(dispatch_get_main_queue(), ^{ + [UIApplication.sharedApplication.keyWindow.rootViewController presentViewController:ac animated:YES completion:nil]; + // [self presentViewController:ac animated:YES completion:nil]; + }); + } else { + // Fallback on earlier versions + } + + +} + +@end diff --git a/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboard@2x.png b/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboard@2x.png deleted file mode 100644 index dfea3ade9..000000000 Binary files a/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboard@2x.png and /dev/null differ diff --git a/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboard@3x.png b/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboard@3x.png index d202f4a66..316e9d8b3 100644 Binary files a/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboard@3x.png and b/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboard@3x.png differ diff --git a/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboardHL@2x.png b/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboardHL@2x.png deleted file mode 100644 index dfea3ade9..000000000 Binary files a/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboardHL@2x.png and /dev/null differ diff --git a/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboardHL@3x.png b/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboardHL@3x.png index d202f4a66..316e9d8b3 100644 Binary files a/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboardHL@3x.png and b/iOS/TUIKit/TUIChat/Resources/TUIChat.bundle/ToolViewKeyboardHL@3x.png differ diff --git a/iOS/TUIKit/TUIChat/TUIChat.podspec b/iOS/TUIKit/TUIChat/TUIChat.podspec deleted file mode 100644 index b45d5739d..000000000 --- a/iOS/TUIKit/TUIChat/TUIChat.podspec +++ /dev/null @@ -1,25 +0,0 @@ -Pod::Spec.new do |spec| - spec.name = 'TUIChat' - spec.version = '1.0.0' - spec.platform = :ios - spec.ios.deployment_target = '9.0' - spec.license = { :type => 'Proprietary', - :text => <<-LICENSE - copyright 2017 tencent Ltd. All rights reserved. - LICENSE - } - spec.homepage = 'https://cloud.tencent.com/document/product/269/3794' - spec.documentation_url = 'https://cloud.tencent.com/document/product/269/9147' - spec.authors = 'tencent video cloud' - spec.summary = 'TUIChat' - spec.dependency 'TXIMSDK_Plus_iOS' - - spec.requires_arc = true - - spec.vendored_libraries = ['VoiceConvert/libopencore-amrnb.a', 'VoiceConvert/libopencore-amrwb.a'] - spec.source = { :git => 'https://git.woa.com/lynxzhang/tui-components.git', :tag => spec.version} - spec.source_files = '**/*.{h,m,mm,c}' - spec.resource = [ - 'Resources/*.bundle' - ] -end diff --git a/iOS/TUIKit/TUIChat/UI/Chat/TUIBaseChatViewController+AuthControl.h b/iOS/TUIKit/TUIChat/UI/Chat/TUIBaseChatViewController+AuthControl.h new file mode 100644 index 000000000..9a00113e6 --- /dev/null +++ b/iOS/TUIKit/TUIChat/UI/Chat/TUIBaseChatViewController+AuthControl.h @@ -0,0 +1,29 @@ +// +// TUIBaseChatViewController+AuthControl.h +// TUIChat +// +// Created by wyl on 2022/2/14. +// + +#import "TUIBaseChatViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface TUIBaseChatViewController (AuthControl) +- (void)selectPhotoForSend;//可选 selectPhotoForSendV2 +- (void)takePictureForSend; +- (void)takeVideoForSend; +- (void)selectFileForSend; + +/* + // selectPhotoForSendV2 方法 支持了iOS隐私14新API,使用了全新的PHPickerViewController提供隐私安全。 + // 同时向下兼容使用UIImagePickerController进行图文发送 + // 如需支持多选发送,可在v2版本基础上开发. + // 注:模拟器上因为一张图片含有heic的存在可能导致无法正常发送,真机无误 + //There is a separate JEPG transcoding issue that only affects the simulator (63426347), please refer to https://developer.apple.com/forums/thread/658135 for more information. + */ +- (void)selectPhotoForSendV2; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS/TUIKit/TUIChat/UI/Chat/TUIBaseChatViewController+AuthControl.m b/iOS/TUIKit/TUIChat/UI/Chat/TUIBaseChatViewController+AuthControl.m new file mode 100644 index 000000000..6f0ce5801 --- /dev/null +++ b/iOS/TUIKit/TUIChat/UI/Chat/TUIBaseChatViewController+AuthControl.m @@ -0,0 +1,599 @@ +// +// TUIBaseChatViewController+AuthControl.m +// TUIChat +// +// Created by wyl on 2022/2/14. +// + +#import "TUIBaseChatViewController+AuthControl.h" +#import +#import +#import +#import +#import "ReactiveObjC/ReactiveObjC.h" +#import "TUIMessageController.h" +#import "TUIImageMessageCellData.h" +#import "TUIVideoMessageCellData.h" +#import "TUIFileMessageCellData.h" +#import "TUIVoiceMessageCellData.h" +#import "TUIDefine.h" +#import "TUIMessageMultiChooseView.h" +#import "TUIMessageSearchController.h" +#import "TUIChatDataProvider.h" +#import "TUIMessageDataProvider.h" +#import "TUICameraViewController.h" +#import "TUITool.h" +#import "TUICore.h" +#import "TUIDefine.h" +#import "NSDictionary+TUISafe.h" +#import "NSString+emoji.h" +#import "TUIThemeManager.h" +#import "PhotosUI/PhotosUI.h" +#import "TUIUserAuthorizationCenter.h" + +@interface TUIBaseChatViewController (AuthControl) +@end +@implementation TUIBaseChatViewController (AuthControl) +#pragma mark - UIImagePickerController & UIDocumentPickerViewController +- (void)selectPhotoForSend +{ + if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) { + UIImagePickerController *picker = [[UIImagePickerController alloc] init]; + picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; + picker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypePhotoLibrary]; + picker.delegate = self; + [self presentViewController:picker animated:YES completion:nil]; + } +} + +- (void)takePictureForSend +{ + __weak typeof(self)weakSelf = self; + + void(^actionBlock)(void) = ^(void) { + __strong typeof(weakSelf)strongSelf = weakSelf; + TUICameraViewController *vc = [[TUICameraViewController alloc] init]; + vc.type = TUICameraMediaTypePhoto; + vc.delegate = strongSelf; + [strongSelf.navigationController pushViewController:vc animated:YES]; + }; + if ([TUIUserAuthorizationCenter isEnableMicroAuthorization] && + [TUIUserAuthorizationCenter isEnableCameraAuthorization]) { + dispatch_async(dispatch_get_main_queue(), ^{ + actionBlock(); + }); + } + else { + if (![TUIUserAuthorizationCenter isEnableMicroAuthorization]) { + [TUIUserAuthorizationCenter microStateActionWithPopCompletion:^{ + if ([TUIUserAuthorizationCenter isEnableCameraAuthorization]) { + dispatch_async(dispatch_get_main_queue(), ^{ + actionBlock(); + }); + } + }]; + } + if (![TUIUserAuthorizationCenter isEnableCameraAuthorization]) { + [TUIUserAuthorizationCenter cameraStateActionWithPopCompletion:^{ + if ([TUIUserAuthorizationCenter isEnableMicroAuthorization]) { + dispatch_async(dispatch_get_main_queue(), ^{ + actionBlock(); + }); + } + }]; + }; + } + +} + +- (void)takeVideoForSend +{ + __weak typeof(self)weakSelf = self; + void(^actionBlock)(void) = ^(void) { + __strong typeof(weakSelf)strongSelf = weakSelf; + TUICameraViewController *vc = [[TUICameraViewController alloc] init]; + vc.type = TUICameraMediaTypeVideo; + vc.videoMinimumDuration = 1.5; + vc.delegate = strongSelf; + [strongSelf.navigationController pushViewController:vc animated:YES]; + }; + + if ([TUIUserAuthorizationCenter isEnableMicroAuthorization] && + [TUIUserAuthorizationCenter isEnableCameraAuthorization]) { + dispatch_async(dispatch_get_main_queue(), ^{ + actionBlock(); + }); + } + else { + if (![TUIUserAuthorizationCenter isEnableMicroAuthorization]) { + [TUIUserAuthorizationCenter microStateActionWithPopCompletion:^{ + if ([TUIUserAuthorizationCenter isEnableCameraAuthorization]) { + dispatch_async(dispatch_get_main_queue(), ^{ + actionBlock(); + }); + } + }]; + } + if (![TUIUserAuthorizationCenter isEnableCameraAuthorization]) { + [TUIUserAuthorizationCenter cameraStateActionWithPopCompletion:^{ + if ([TUIUserAuthorizationCenter isEnableMicroAuthorization]) { + dispatch_async(dispatch_get_main_queue(), ^{ + actionBlock(); + }); + } + }]; + } + } + + +} + +- (void)selectFileForSend +{ + UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[(NSString *)kUTTypeData] inMode:UIDocumentPickerModeOpen]; + picker.delegate = self; + [self presentViewController:picker animated:YES completion:nil]; + +} + +- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info +{ + // 快速点的时候会回调多次 + @weakify(self) + picker.delegate = nil; + [picker dismissViewControllerAnimated:YES completion:^{ + @strongify(self) + NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType]; + if([mediaType isEqualToString:(NSString *)kUTTypeImage]){ + UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; + UIImageOrientation imageOrientation = image.imageOrientation; + if(imageOrientation != UIImageOrientationUp) + { + CGFloat aspectRatio = MIN ( 1920 / image.size.width, 1920 / image.size.height ); + CGFloat aspectWidth = image.size.width * aspectRatio; + CGFloat aspectHeight = image.size.height * aspectRatio; + + UIGraphicsBeginImageContext(CGSizeMake(aspectWidth, aspectHeight)); + [image drawInRect:CGRectMake(0, 0, aspectWidth, aspectHeight)]; + image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + } + + NSData *data = UIImageJPEGRepresentation(image, 0.75); + NSString *path = [TUIKit_Image_Path stringByAppendingString:[TUITool genImageName:nil]]; + [[NSFileManager defaultManager] createFileAtPath:path contents:data attributes:nil]; + + V2TIMMessage *message = [[V2TIMManager sharedInstance] createImageMessage:path]; + [self sendMessage:message]; + if (self.delegate && [self.delegate respondsToSelector:@selector(chatController:didSendMessage:)]) { + [self.delegate chatController:self didSendMessage:message]; + } + } + else if([mediaType isEqualToString:(NSString *)kUTTypeMovie]){ + NSURL *url = [info objectForKey:UIImagePickerControllerMediaURL]; + if (url) { + [self transcodeIfNeed:url]; + return; + } + + // 在某些情况下,UIImagePickerControllerMediaURL 可能为空,使用 UIImagePickerControllerPHAsset + PHAsset *asset = nil; + if (@available(iOS 11.0, *)) { + asset = [info objectForKey:UIImagePickerControllerPHAsset]; + } + if (asset) { + [self originURLWithAsset:asset completion:^(BOOL success, NSURL *URL) { + if (success) { + [self transcodeIfNeed:URL]; + return; + } + }]; + return; + } + + // 在 ios 12 的情况下,UIImagePickerControllerMediaURL 及 UIImagePickerControllerPHAsset 可能为空,需要使用其他方式获取视频文件原始路径 + url = [info objectForKey:UIImagePickerControllerReferenceURL]; + if (url) { + [self originURLWithRefrenceURL:url completion:^(BOOL success, NSURL *URL) { + if (success) { + [self transcodeIfNeed:URL]; + } + }]; + return; + } + + // 其他,不支持 + [self.view makeToast:@"not support this video"]; + } + }]; +} + +// 根据 UIImagePickerControllerReferenceURL 获取原始文件路径 +- (void)originURLWithRefrenceURL:(NSURL *)URL completion:(void(^)(BOOL success, NSURL *URL))completion +{ + if (completion == nil) { + return; + } + NSDictionary *queryInfo = [self dictionaryWithURLQuery:URL.query]; + NSString *fileName = @"temp.mp4"; + if ([queryInfo.allKeys containsObject:@"id"] && [queryInfo.allKeys containsObject:@"ext"]) { + fileName = [NSString stringWithFormat:@"%@.%@", queryInfo[@"id"], [queryInfo[@"ext"] lowercaseString]]; + } + NSString* tempPath = NSTemporaryDirectory(); + NSString *filePath = [tempPath stringByAppendingPathComponent:fileName]; + if ([NSFileManager.defaultManager isDeletableFileAtPath:filePath]) { + [NSFileManager.defaultManager removeItemAtPath:filePath error:nil]; + } + NSURL *newUrl = [NSURL fileURLWithPath:filePath]; + ALAssetsLibrary *assetLibrary= [[ALAssetsLibrary alloc] init]; + [assetLibrary assetForURL:URL resultBlock:^(ALAsset *asset) { + if (asset == nil) { + completion(NO, nil); + return; + } + ALAssetRepresentation *rep = [asset defaultRepresentation]; + Byte *buffer = (Byte*)malloc(rep.size); + NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:rep.size error:nil]; + NSData *data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];//this is NSData may be what you want + BOOL flag = [NSFileManager.defaultManager createFileAtPath:filePath contents:data attributes:nil]; + completion(flag, newUrl); + } failureBlock:^(NSError *err) { + completion(NO, nil); + }]; +} + +- (void)originURLWithAsset:(PHAsset *)asset completion:(void(^)(BOOL success, NSURL *URL))completion +{ + if (completion == nil) { + return; + } + [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { + if (status != PHAuthorizationStatusAuthorized) { + completion(NO, nil); + return; + } + + NSArray *resources = [PHAssetResource assetResourcesForAsset:asset]; + if (resources.count == 0) { + completion(NO, nil); + return; + } + + PHAssetResourceRequestOptions *options = [[PHAssetResourceRequestOptions alloc] init]; + options.networkAccessAllowed = NO; + __block BOOL invoked = NO; + [PHAssetResourceManager.defaultManager requestDataForAssetResource:resources.firstObject options:options dataReceivedHandler:^(NSData * _Nonnull data) { + // 此处会有重复回调的问题 + if (invoked) { + return; + } + invoked = YES; + if (data == nil) { + completion(NO, nil); + return; + } + NSString *fileName = @"temp.mp4"; + NSString* tempPath = NSTemporaryDirectory(); + NSString *filePath = [tempPath stringByAppendingPathComponent:fileName]; + if ([NSFileManager.defaultManager isDeletableFileAtPath:filePath]) { + [NSFileManager.defaultManager removeItemAtPath:filePath error:nil]; + } + NSURL *newUrl = [NSURL fileURLWithPath:filePath]; + BOOL flag = [NSFileManager.defaultManager createFileAtPath:filePath contents:data attributes:nil]; + completion(flag, newUrl); + } completionHandler:^(NSError * _Nullable error) { + completion(NO, nil); + }]; + }]; +} + +// 获取 NSURL 查询字符串信息 +- (NSDictionary *)dictionaryWithURLQuery:(NSString *)query +{ + NSArray *components = [query componentsSeparatedByString:@"&"]; + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + for (NSString *item in components) { + NSArray *subs = [item componentsSeparatedByString:@"="]; + if (subs.count == 2) { + [dict setObject:subs.lastObject forKey:subs.firstObject]; + } + } + return [NSDictionary dictionaryWithDictionary:dict];; +} + +// 转码 +- (void)transcodeIfNeed:(NSURL *)url +{ + if ([url.pathExtension.lowercaseString isEqualToString:@"mp4"]) { + // mp4 直接发送 + [self sendVideoWithUrl:url]; + } else { + // 非 mp4 文件 => mp4 文件 + NSString* tempPath = NSTemporaryDirectory(); + NSURL *urlName = [url URLByDeletingPathExtension]; + NSURL *newUrl = [NSURL URLWithString:[NSString stringWithFormat:@"file://%@%@.mp4", tempPath,[urlName.lastPathComponent stringByRemovingPercentEncoding]]]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:newUrl.path]){ + NSError *error; + BOOL success = [fileManager removeItemAtPath:newUrl.path error:&error]; + if (!success || error) { + NSAssert1(NO, @"removeItemFail: %@", error.localizedDescription); + return; + } + } + // mov to mp4 + AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil]; + AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:avAsset presetName:AVAssetExportPresetHighestQuality]; + exportSession.outputURL = newUrl; + exportSession.outputFileType = AVFileTypeMPEG4; + exportSession.shouldOptimizeForNetworkUse = YES; + + [exportSession exportAsynchronouslyWithCompletionHandler:^{ + switch ([exportSession status]) + { + case AVAssetExportSessionStatusFailed: + NSLog(@"Export session failed"); + break; + case AVAssetExportSessionStatusCancelled: + NSLog(@"Export canceled"); + break; + case AVAssetExportSessionStatusCompleted: + { + //Video conversion finished + NSLog(@"Successful!"); + dispatch_async(dispatch_get_main_queue(), ^{ + [self sendVideoWithUrl:newUrl]; + }); + } + break; + default: + break; + } + }]; + } +} + +- (void)sendVideoWithUrl:(NSURL*)url { + [TUITool dispatchMainAsync:^{ + V2TIMMessage *message = [TUIMessageDataProvider getVideoMessageWithURL:url]; + [self sendMessage:message]; + if (self.delegate && [self.delegate respondsToSelector:@selector(chatController:didSendMessage:)]) { + [self.delegate chatController:self didSendMessage:message]; + } + }]; +} + +- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker +{ + [picker dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url +{ + [url startAccessingSecurityScopedResource]; + NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init]; + NSError *error; + @weakify(self) + [coordinator coordinateReadingItemAtURL:url options:0 error:&error byAccessor:^(NSURL *newURL) { + @strongify(self) + NSData *fileData = [NSData dataWithContentsOfURL:url]; + NSString *fileName = [url lastPathComponent]; + NSString *filePath = [TUIKit_File_Path stringByAppendingString:fileName]; + if ([NSFileManager.defaultManager fileExistsAtPath:filePath]) { + // 存在同名文件,对文件名进行递增 + int i = 0; + NSArray *arrayM = [NSFileManager.defaultManager subpathsAtPath:TUIKit_File_Path]; + for (NSString *sub in arrayM) { + if ([sub.pathExtension isEqualToString:fileName.pathExtension] && + [sub.stringByDeletingPathExtension containsString:fileName.stringByDeletingPathExtension]) { + i++; + } + } + if (i) { + fileName = [fileName stringByReplacingOccurrencesOfString:fileName.stringByDeletingPathExtension withString:[NSString stringWithFormat:@"%@(%d)", fileName.stringByDeletingPathExtension, i]]; + filePath = [TUIKit_File_Path stringByAppendingString:fileName]; + } + } + [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileData attributes:nil]; + if([[NSFileManager defaultManager] fileExistsAtPath:filePath]){ + unsigned long long fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil] fileSize]; + + V2TIMMessage *message = [[V2TIMManager sharedInstance] createFileMessage:filePath fileName:fileName]; + [self sendMessage:message]; + if (self.delegate && [self.delegate respondsToSelector:@selector(chatController:didSendMessage:)]) { + [self.delegate chatController:self didSendMessage:message]; + } + } + }]; + [url stopAccessingSecurityScopedResource]; + [controller dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller +{ + [controller dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - TUICameraViewControllerDelegate +- (void)cameraViewController:(TUICameraViewController *)controller didFinishPickingMediaWithVideoURL:(NSURL *)url { + [self transcodeIfNeed:url]; +} + +- (void)cameraViewController:(TUICameraViewController *)controller didFinishPickingMediaWithImage:(UIImage *)image { + NSData *data = UIImageJPEGRepresentation(image, 0.75); + NSString *path = [TUIKit_Image_Path stringByAppendingString:[TUITool genImageName:nil]]; + [[NSFileManager defaultManager] createFileAtPath:path contents:data attributes:nil]; + + V2TIMMessage *message = [[V2TIMManager sharedInstance] createImageMessage:path]; + [self sendMessage:message]; + if (self.delegate && [self.delegate respondsToSelector:@selector(chatController:didSendMessage:)]) { + [self.delegate chatController:self didSendMessage:message]; + } +} + +- (void)cameraViewControllerDidCancel:(TUICameraViewController *)controller { +} + +//MARK: iOS14使用新的相册管理器 +- (void)selectPhotoForSendV2 { + + if (@available(iOS 14, *)) { + PHAccessLevel level = PHAccessLevelReadWrite; + PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatusForAccessLevel:level]; + switch (status) { + case PHAuthorizationStatusLimited: + NSLog(@"limited"); + [self _takeImagePhoto]; + break; + case PHAuthorizationStatusDenied: + [TUIUserAuthorizationCenter showAlert:TUIChatAuthControlTypePhoto]; + break; + case PHAuthorizationStatusAuthorized: + NSLog(@"authorized"); + [self _takeImagePhoto]; + break; + case PHAuthorizationStatusNotDetermined: + NSLog(@"denied"); + [self _requestPhotoAuthorization]; + break; + + default: + break; + } + } else { + //判断相册权限 + PHAuthorizationStatus photoAuthorStatus = [PHPhotoLibrary authorizationStatus]; + if (photoAuthorStatus ==PHAuthorizationStatusAuthorized ) { + [self _takeImagePhoto]; + }else{ + [self _requestPhotoAuthorization]; + } + } + +} + +- (void)_requestPhotoAuthorization { + if (@available(iOS 14, *)) { + PHAccessLevel level = PHAccessLevelReadWrite; + // 请求权限,需注意 limited 权限仅在 accessLevel 为 readAndWrite 时生效 + [PHPhotoLibrary requestAuthorizationForAccessLevel:level handler:^(PHAuthorizationStatus status) { + switch (status) { + case PHAuthorizationStatusLimited: { + NSLog(@"limited"); + } + break; + case PHAuthorizationStatusDenied: + NSLog(@"denied"); + [TUIUserAuthorizationCenter showAlert:TUIChatAuthControlTypePhoto]; + break; + case PHAuthorizationStatusAuthorized: + NSLog(@"authorized"); + [self _takeImagePhoto]; + break; + default: + break; + } + }]; + } else { + //获取相册访问权限 ios8之后推荐用这种方法 //该方法提示用户授权对相册的访问 + [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { + if (status == PHAuthorizationStatusDenied) { + [TUIUserAuthorizationCenter showAlert:TUIChatAuthControlTypePhoto]; + }else if (status == PHAuthorizationStatusAuthorized){ + //有权限 可直接跳转 + [self _takeImagePhoto]; + } + }]; + } +} + +- (void)_takeImagePhoto { + dispatch_async(dispatch_get_main_queue(), ^{ + if (@available(iOS 14.0, *)) { + PHPickerConfiguration *configuration = [[PHPickerConfiguration alloc] init]; + configuration.filter = [PHPickerFilter anyFilterMatchingSubfilters:@[[PHPickerFilter imagesFilter], + [PHPickerFilter videosFilter]]]; + configuration.selectionLimit = 1; // 默认为1,为0时表示可多选。 + PHPickerViewController *picker = [[PHPickerViewController alloc] initWithConfiguration:configuration]; + picker.delegate = self; + picker.modalPresentationStyle = UIModalPresentationFullScreen; + picker.view.backgroundColor = [UIColor whiteColor]; + [self presentViewController:picker animated:YES completion:^{ + }]; + } else { + + if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) { + UIImagePickerController *picker = [[UIImagePickerController alloc] init]; + picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; + picker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypePhotoLibrary]; + picker.delegate = self; + [self presentViewController:picker animated:YES completion:nil]; + } + } + }); +} + +//MARK: PHPickerViewControllerDelegate + +- (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray *)results API_AVAILABLE(ios(14)){ + + dispatch_async(dispatch_get_main_queue(), ^{ + [picker dismissViewControllerAnimated:YES completion:nil]; + }); + + if(!results || !results.count) { + return; + }else{ + PHPickerResult *result = [results firstObject]; + NSItemProvider *itemProvoider = result.itemProvider; + __weak typeof(self) weakSelf = self; + if ([itemProvoider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) { + [itemProvoider loadDataRepresentationForTypeIdentifier:(NSString *)kUTTypeImage completionHandler:^(NSData * _Nullable data, NSError * _Nullable error) { + __strong typeof(self) strongSelf = weakSelf; + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *path = [TUIKit_Image_Path stringByAppendingString:[TUITool genImageName:nil]]; + [[NSFileManager defaultManager] createFileAtPath:path contents:data attributes:nil]; + V2TIMMessage *message = [[V2TIMManager sharedInstance] createImageMessage:path]; + [strongSelf sendMessage:message]; + if (strongSelf.delegate && [strongSelf.delegate respondsToSelector:@selector(chatController:didSendMessage:)]) { + [strongSelf.delegate chatController:strongSelf didSendMessage:message]; + } + }); + }]; + } + else if ([itemProvoider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeMovie]) { + [itemProvoider loadDataRepresentationForTypeIdentifier:(NSString *)kUTTypeMovie completionHandler:^(NSData * _Nullable data, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *fileName = @"temp.mp4"; + NSString* tempPath = NSTemporaryDirectory(); + NSString *filePath = [tempPath stringByAppendingPathComponent:fileName]; + if ([NSFileManager.defaultManager isDeletableFileAtPath:filePath]) { + [NSFileManager.defaultManager removeItemAtPath:filePath error:nil]; + } + NSURL *newUrl = [NSURL fileURLWithPath:filePath]; + BOOL flag = [NSFileManager.defaultManager createFileAtPath:filePath contents:data attributes:nil]; + if (flag) { + [self transcodeIfNeed:newUrl]; + } + }); + }]; + } + else { + NSString * typeIdentifier = result.itemProvider.registeredTypeIdentifiers.firstObject; + [itemProvoider loadFileRepresentationForTypeIdentifier:typeIdentifier completionHandler:^(NSURL * _Nullable url, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + UIImage * result; + NSData * data = [NSData dataWithContentsOfURL:url]; + result = [UIImage imageWithData:data]; + //注:模拟器上 typeIdentifier 为public.jepg时拿不到url + //见苹果developer原话:There is a separate JEPG transcoding issue that only affects the simulator (63426347), please refer to https://developer.apple.com/forums/thread/658135 for more information. + }); + }]; + } + } +} + + +@end + diff --git a/iOS/TUIKit/TUIChat/UI/Chat/TUIBaseChatViewController.m b/iOS/TUIKit/TUIChat/UI/Chat/TUIBaseChatViewController.m index b22776d10..0fc2627be 100644 --- a/iOS/TUIKit/TUIChat/UI/Chat/TUIBaseChatViewController.m +++ b/iOS/TUIKit/TUIChat/UI/Chat/TUIBaseChatViewController.m @@ -28,8 +28,9 @@ #import "NSDictionary+TUISafe.h" #import "NSString+emoji.h" #import "TUIThemeManager.h" +#import "TUIBaseChatViewController+AuthControl.h" -@interface TUIBaseChatViewController () +@interface TUIBaseChatViewController () @property (nonatomic, strong) TUIMessageMultiChooseView *multiChooseView; @property (nonatomic, assign) BOOL responseKeyboard; // @{@"serviceID" : serviceID, @"title" : @"视频通话", @"image" : image} @@ -344,7 +345,7 @@ return; } if (cell.data == [TUIInputMoreCellData photoData]) { - [self selectPhotoForSend]; + [self selectPhotoForSendV2]; } else if (cell.data == [TUIInputMoreCellData videoData]) { [self takeVideoForSend]; @@ -434,343 +435,8 @@ } } -#pragma mark - UIImagePickerController & UIDocumentPickerViewController -- (void)selectPhotoForSend -{ - if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) { - UIImagePickerController *picker = [[UIImagePickerController alloc] init]; - picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; - picker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypePhotoLibrary]; - picker.delegate = self; - [self presentViewController:picker animated:YES completion:nil]; - } -} -- (void)takePictureForSend -{ - TUICameraViewController *vc = [[TUICameraViewController alloc] init]; - vc.type = TUICameraMediaTypePhoto; - vc.delegate = self; - [self.navigationController pushViewController:vc animated:YES]; -} -- (void)takeVideoForSend -{ - TUICameraViewController *vc = [[TUICameraViewController alloc] init]; - vc.type = TUICameraMediaTypeVideo; - vc.videoMinimumDuration = 1.5; - vc.delegate = self; - [self.navigationController pushViewController:vc animated:YES]; -} - -- (void)selectFileForSend -{ - UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[(NSString *)kUTTypeData] inMode:UIDocumentPickerModeOpen]; - picker.delegate = self; - [self presentViewController:picker animated:YES completion:nil]; - -} - -- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info -{ - // 快速点的时候会回调多次 - @weakify(self) - picker.delegate = nil; - [picker dismissViewControllerAnimated:YES completion:^{ - @strongify(self) - NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType]; - if([mediaType isEqualToString:(NSString *)kUTTypeImage]){ - UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; - UIImageOrientation imageOrientation = image.imageOrientation; - if(imageOrientation != UIImageOrientationUp) - { - CGFloat aspectRatio = MIN ( 1920 / image.size.width, 1920 / image.size.height ); - CGFloat aspectWidth = image.size.width * aspectRatio; - CGFloat aspectHeight = image.size.height * aspectRatio; - - UIGraphicsBeginImageContext(CGSizeMake(aspectWidth, aspectHeight)); - [image drawInRect:CGRectMake(0, 0, aspectWidth, aspectHeight)]; - image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - } - - NSData *data = UIImageJPEGRepresentation(image, 0.75); - NSString *path = [TUIKit_Image_Path stringByAppendingString:[TUITool genImageName:nil]]; - [[NSFileManager defaultManager] createFileAtPath:path contents:data attributes:nil]; - - V2TIMMessage *message = [[V2TIMManager sharedInstance] createImageMessage:path]; - [self sendMessage:message]; - if (self.delegate && [self.delegate respondsToSelector:@selector(chatController:didSendMessage:)]) { - [self.delegate chatController:self didSendMessage:message]; - } - } - else if([mediaType isEqualToString:(NSString *)kUTTypeMovie]){ - NSURL *url = [info objectForKey:UIImagePickerControllerMediaURL]; - if (url) { - [self transcodeIfNeed:url]; - return; - } - - // 在某些情况下,UIImagePickerControllerMediaURL 可能为空,使用 UIImagePickerControllerPHAsset - PHAsset *asset = nil; - if (@available(iOS 11.0, *)) { - asset = [info objectForKey:UIImagePickerControllerPHAsset]; - } - if (asset) { - [self originURLWithAsset:asset completion:^(BOOL success, NSURL *URL) { - if (success) { - [self transcodeIfNeed:URL]; - return; - } - }]; - return; - } - - // 在 ios 12 的情况下,UIImagePickerControllerMediaURL 及 UIImagePickerControllerPHAsset 可能为空,需要使用其他方式获取视频文件原始路径 - url = [info objectForKey:UIImagePickerControllerReferenceURL]; - if (url) { - [self originURLWithRefrenceURL:url completion:^(BOOL success, NSURL *URL) { - if (success) { - [self transcodeIfNeed:URL]; - } - }]; - return; - } - - // 其他,不支持 - [self.view makeToast:@"not support this video"]; - } - }]; -} - -// 根据 UIImagePickerControllerReferenceURL 获取原始文件路径 -- (void)originURLWithRefrenceURL:(NSURL *)URL completion:(void(^)(BOOL success, NSURL *URL))completion -{ - if (completion == nil) { - return; - } - NSDictionary *queryInfo = [self dictionaryWithURLQuery:URL.query]; - NSString *fileName = @"temp.mp4"; - if ([queryInfo.allKeys containsObject:@"id"] && [queryInfo.allKeys containsObject:@"ext"]) { - fileName = [NSString stringWithFormat:@"%@.%@", queryInfo[@"id"], [queryInfo[@"ext"] lowercaseString]]; - } - NSString* tempPath = NSTemporaryDirectory(); - NSString *filePath = [tempPath stringByAppendingPathComponent:fileName]; - if ([NSFileManager.defaultManager isDeletableFileAtPath:filePath]) { - [NSFileManager.defaultManager removeItemAtPath:filePath error:nil]; - } - NSURL *newUrl = [NSURL fileURLWithPath:filePath]; - ALAssetsLibrary *assetLibrary= [[ALAssetsLibrary alloc] init]; - [assetLibrary assetForURL:URL resultBlock:^(ALAsset *asset) { - if (asset == nil) { - completion(NO, nil); - return; - } - ALAssetRepresentation *rep = [asset defaultRepresentation]; - Byte *buffer = (Byte*)malloc(rep.size); - NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:rep.size error:nil]; - NSData *data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];//this is NSData may be what you want - BOOL flag = [NSFileManager.defaultManager createFileAtPath:filePath contents:data attributes:nil]; - completion(flag, newUrl); - } failureBlock:^(NSError *err) { - completion(NO, nil); - }]; -} - -- (void)originURLWithAsset:(PHAsset *)asset completion:(void(^)(BOOL success, NSURL *URL))completion -{ - if (completion == nil) { - return; - } - [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { - if (status != PHAuthorizationStatusAuthorized) { - completion(NO, nil); - return; - } - - NSArray *resources = [PHAssetResource assetResourcesForAsset:asset]; - if (resources.count == 0) { - completion(NO, nil); - return; - } - - PHAssetResourceRequestOptions *options = [[PHAssetResourceRequestOptions alloc] init]; - options.networkAccessAllowed = NO; - __block BOOL invoked = NO; - [PHAssetResourceManager.defaultManager requestDataForAssetResource:resources.firstObject options:options dataReceivedHandler:^(NSData * _Nonnull data) { - // 此处会有重复回调的问题 - if (invoked) { - return; - } - invoked = YES; - if (data == nil) { - completion(NO, nil); - return; - } - NSString *fileName = @"temp.mp4"; - NSString* tempPath = NSTemporaryDirectory(); - NSString *filePath = [tempPath stringByAppendingPathComponent:fileName]; - if ([NSFileManager.defaultManager isDeletableFileAtPath:filePath]) { - [NSFileManager.defaultManager removeItemAtPath:filePath error:nil]; - } - NSURL *newUrl = [NSURL fileURLWithPath:filePath]; - BOOL flag = [NSFileManager.defaultManager createFileAtPath:filePath contents:data attributes:nil]; - completion(flag, newUrl); - } completionHandler:^(NSError * _Nullable error) { - completion(NO, nil); - }]; - }]; -} - -// 获取 NSURL 查询字符串信息 -- (NSDictionary *)dictionaryWithURLQuery:(NSString *)query -{ - NSArray *components = [query componentsSeparatedByString:@"&"]; - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - for (NSString *item in components) { - NSArray *subs = [item componentsSeparatedByString:@"="]; - if (subs.count == 2) { - [dict setObject:subs.lastObject forKey:subs.firstObject]; - } - } - return [NSDictionary dictionaryWithDictionary:dict];; -} - -// 转码 -- (void)transcodeIfNeed:(NSURL *)url -{ - if ([url.pathExtension.lowercaseString isEqualToString:@"mp4"]) { - // mp4 直接发送 - [self sendVideoWithUrl:url]; - } else { - // 非 mp4 文件 => mp4 文件 - NSString* tempPath = NSTemporaryDirectory(); - NSURL *urlName = [url URLByDeletingPathExtension]; - NSURL *newUrl = [NSURL URLWithString:[NSString stringWithFormat:@"file://%@%@.mp4", tempPath,[urlName.lastPathComponent stringByRemovingPercentEncoding]]]; - - NSFileManager *fileManager = [NSFileManager defaultManager]; - if ([fileManager fileExistsAtPath:newUrl.path]){ - NSError *error; - BOOL success = [fileManager removeItemAtPath:newUrl.path error:&error]; - if (!success || error) { - NSAssert1(NO, @"removeItemFail: %@", error.localizedDescription); - return; - } - } - // mov to mp4 - AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil]; - AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:avAsset presetName:AVAssetExportPresetHighestQuality]; - exportSession.outputURL = newUrl; - exportSession.outputFileType = AVFileTypeMPEG4; - exportSession.shouldOptimizeForNetworkUse = YES; - - [exportSession exportAsynchronouslyWithCompletionHandler:^{ - switch ([exportSession status]) - { - case AVAssetExportSessionStatusFailed: - NSLog(@"Export session failed"); - break; - case AVAssetExportSessionStatusCancelled: - NSLog(@"Export canceled"); - break; - case AVAssetExportSessionStatusCompleted: - { - //Video conversion finished - NSLog(@"Successful!"); - dispatch_async(dispatch_get_main_queue(), ^{ - [self sendVideoWithUrl:newUrl]; - }); - } - break; - default: - break; - } - }]; - } -} - -- (void)sendVideoWithUrl:(NSURL*)url { - [TUITool dispatchMainAsync:^{ - V2TIMMessage *message = [TUIMessageDataProvider getVideoMessageWithURL:url]; - [self sendMessage:message]; - if (self.delegate && [self.delegate respondsToSelector:@selector(chatController:didSendMessage:)]) { - [self.delegate chatController:self didSendMessage:message]; - } - }]; -} - -- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker -{ - [picker dismissViewControllerAnimated:YES completion:nil]; -} - -- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url -{ - [url startAccessingSecurityScopedResource]; - NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init]; - NSError *error; - @weakify(self) - [coordinator coordinateReadingItemAtURL:url options:0 error:&error byAccessor:^(NSURL *newURL) { - @strongify(self) - NSData *fileData = [NSData dataWithContentsOfURL:url]; - NSString *fileName = [url lastPathComponent]; - NSString *filePath = [TUIKit_File_Path stringByAppendingString:fileName]; - if ([NSFileManager.defaultManager fileExistsAtPath:filePath]) { - // 存在同名文件,对文件名进行递增 - int i = 0; - NSArray *arrayM = [NSFileManager.defaultManager subpathsAtPath:TUIKit_File_Path]; - for (NSString *sub in arrayM) { - if ([sub.pathExtension isEqualToString:fileName.pathExtension] && - [sub.stringByDeletingPathExtension containsString:fileName.stringByDeletingPathExtension]) { - i++; - } - } - if (i) { - fileName = [fileName stringByReplacingOccurrencesOfString:fileName.stringByDeletingPathExtension withString:[NSString stringWithFormat:@"%@(%d)", fileName.stringByDeletingPathExtension, i]]; - filePath = [TUIKit_File_Path stringByAppendingString:fileName]; - } - } - [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileData attributes:nil]; - if([[NSFileManager defaultManager] fileExistsAtPath:filePath]){ - unsigned long long fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil] fileSize]; - - V2TIMMessage *message = [[V2TIMManager sharedInstance] createFileMessage:filePath fileName:fileName]; - [self sendMessage:message]; - if (self.delegate && [self.delegate respondsToSelector:@selector(chatController:didSendMessage:)]) { - [self.delegate chatController:self didSendMessage:message]; - } - } - }]; - [url stopAccessingSecurityScopedResource]; - [controller dismissViewControllerAnimated:YES completion:nil]; -} - -- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller -{ - [controller dismissViewControllerAnimated:YES completion:nil]; -} - -#pragma mark - TUICameraViewControllerDelegate -- (void)cameraViewController:(TUICameraViewController *)controller didFinishPickingMediaWithVideoURL:(NSURL *)url { - [self transcodeIfNeed:url]; -} - -- (void)cameraViewController:(TUICameraViewController *)controller didFinishPickingMediaWithImage:(UIImage *)image { - NSData *data = UIImageJPEGRepresentation(image, 0.75); - NSString *path = [TUIKit_Image_Path stringByAppendingString:[TUITool genImageName:nil]]; - [[NSFileManager defaultManager] createFileAtPath:path contents:data attributes:nil]; - - V2TIMMessage *message = [[V2TIMManager sharedInstance] createImageMessage:path]; - [self sendMessage:message]; - if (self.delegate && [self.delegate respondsToSelector:@selector(chatController:didSendMessage:)]) { - [self.delegate chatController:self didSendMessage:message]; - } -} - -- (void)cameraViewControllerDidCancel:(TUICameraViewController *)controller { -} - -#pragma mark - UIImagePickerControllerDelegate #pragma mark - TUIChatDataProviderForwardDelegate - (NSString *)dataProvider:(TUIChatDataProvider *)dataProvider mergeForwardTitleWithMyName:(NSString *)name { diff --git a/iOS/TUIKit/TUIContact/DataProvider/TUIFindContactViewDataProvider.m b/iOS/TUIKit/TUIContact/DataProvider/TUIFindContactViewDataProvider.m index 4b6e49e9f..9323b52fc 100644 --- a/iOS/TUIKit/TUIContact/DataProvider/TUIFindContactViewDataProvider.m +++ b/iOS/TUIKit/TUIContact/DataProvider/TUIFindContactViewDataProvider.m @@ -75,6 +75,9 @@ cellModel.groupInfo = info; weakSelf.groups = @[cellModel]; } + else { + weakSelf.groups = @[]; + } completion(); } fail:^(int code, NSString *desc) { weakSelf.groups = @[]; diff --git a/iOS/TUIKit/TUIContact/TUIContact.podspec b/iOS/TUIKit/TUIContact/TUIContact.podspec deleted file mode 100644 index 528e362d8..000000000 --- a/iOS/TUIKit/TUIContact/TUIContact.podspec +++ /dev/null @@ -1,25 +0,0 @@ -Pod::Spec.new do |spec| - spec.name = 'TUIContact' - spec.version = '1.0.0' - spec.platform = :ios - spec.ios.deployment_target = '9.0' - spec.license = { :type => 'Proprietary', - :text => <<-LICENSE - copyright 2017 tencent Ltd. All rights reserved. - LICENSE - } - spec.homepage = 'https://cloud.tencent.com/document/product/269/3794' - spec.documentation_url = 'https://cloud.tencent.com/document/product/269/9147' - spec.authors = 'tencent video cloud' - spec.summary = 'TUIContact' - spec.dependency 'TUICore' - - spec.requires_arc = true - - spec.source = { :git => 'https://git.woa.com/lynxzhang/tui-components.git', :tag => spec.version} - spec.source_files = '**/*.{h,m,mm,c}' - - spec.resource = [ - 'Resources/*.bundle' - ] -end diff --git a/iOS/TUIKit/TUIConversation/TUIConversation.podspec b/iOS/TUIKit/TUIConversation/TUIConversation.podspec deleted file mode 100644 index f34ff0843..000000000 --- a/iOS/TUIKit/TUIConversation/TUIConversation.podspec +++ /dev/null @@ -1,25 +0,0 @@ -Pod::Spec.new do |spec| - spec.name = 'TUIConversation' - spec.version = '1.0.0' - spec.platform = :ios - spec.ios.deployment_target = '9.0' - spec.license = { :type => 'Proprietary', - :text => <<-LICENSE - copyright 2017 tencent Ltd. All rights reserved. - LICENSE - } - spec.homepage = 'https://cloud.tencent.com/document/product/269/3794' - spec.documentation_url = 'https://cloud.tencent.com/document/product/269/9147' - spec.authors = 'tencent video cloud' - spec.summary = 'TUIConversation' - spec.dependency 'TUICore' - - spec.requires_arc = true - - spec.source = { :git => 'https://git.woa.com/lynxzhang/tui-components.git', :tag => spec.version} - spec.source_files = '**/*.{h,m,mm,c}' - - spec.resource = [ - 'Resources/*.bundle' - ] -end diff --git a/iOS/TUIKit/TUICore/Resources/TUIKitLocalizable.bundle/Localizable/en.lproj/Localizable.strings b/iOS/TUIKit/TUICore/Resources/TUIKitLocalizable.bundle/Localizable/en.lproj/Localizable.strings index d3010ecd5..6e6d58521 100644 --- a/iOS/TUIKit/TUICore/Resources/TUIKitLocalizable.bundle/Localizable/en.lproj/Localizable.strings +++ b/iOS/TUIKit/TUICore/Resources/TUIKitLocalizable.bundle/Localizable/en.lproj/Localizable.strings @@ -5,6 +5,9 @@ Created by harvy on 2020/10/9. */ +/***************************** 日期格式化 Start *************************/ +"YesterdayDateFormat" = "HH:mma"; // 英文是 HH:mma 不要动 +/***************************** 日期格式化 End ***************************/ "TUIKitTipsMostSelectTextFormat" = "Select %ld at most"; @@ -30,7 +33,6 @@ "Yesterday" = "Yesterday"; "am" = "AM"; "pm" = "PM"; -"YesterdayDateFormat" = "HH:mma"; // 英文是 HH:mma 不要动 "Read" = "Read"; "Unread" = "Unread"; @@ -51,7 +53,7 @@ "Have-been-sent" = "Sent"; "View-the-original" = "View Full Image"; -"TUIKitWelcome" = "Welcome to join the Tencent Cloud Communications family!"; +"TUIKitWelcome" = "Welcome to Tencent Instant Messaging!"; "TUIKitMicCamerAuthTips" = "Enable mic and camera permissions"; "TUIKitMicAuth" = "Enable mic permission"; "TUIKitTipsConfirmResendMessage" = "Resend this message? "; @@ -68,10 +70,7 @@ "TUIKitInputHoldToTalk" = "Hold to Talk"; "TUIKitInputReleaseToSend" = "Release to End"; "TUIKitInputReleaseToCancel" = "Release to Cancel"; -"TUIKitInputNoMicTitle" = "Failed to access mic"; -"TUIKitInputNoMicTips" = "Enable mic permission to send voice messages"; -"TUIKitInputNoMicOperateLater" = "Later"; -"TUIKitInputNoMicOperateEnable" = "Enable"; + "TUIKitInputBlankMessageTitle" = "Unable to send blank message"; "TUIKitInputWillFinishRecordInSeconds" = "Recording will end in %ld seconds."; "TUIKitInputRecordSlideToCancel" = "Slide up to cancel"; @@ -88,7 +87,7 @@ "TUIKitGroupProfileInviteJoin" = "Invite"; "TUIKitGroupProfileAutoApproval" = "Auto Approval"; "TUIKitGroupProfileAlias" = "My Alias in Group"; -"TUIKitGroupProfileMessageDoNotDisturb" = "Message Do Not Disturb"; +"TUIKitGroupProfileMessageDoNotDisturb" = "Mute Notifications"; "TUIKitGroupProfileStickyOnTop" = "Sticky on Top"; "TUIKitGroupProfileDeleteAndExit" = "Delete and Leave"; "TUIKitGroupProfileDissolve" = "Disband Group"; @@ -157,9 +156,9 @@ "TUIKitMoreCamera" = "Take Photo"; "TUIKitMorePhoto" = "Album"; -"TUIKitMoreVideo" = "Video"; +"TUIKitMoreVideo" = "Record Video"; "TUIKitMoreVideoCaptureDurationTip" = "record time is too short"; -"TUIKitMoreFile" = "Files"; +"TUIKitMoreFile" = "File"; "TUIKitMoreVideoCall" = "Video Call"; "TUIKitMoreVoiceCall" = "Voice Call"; "TUIKitMoreGroupLive" = "Group Live"; @@ -185,7 +184,7 @@ "TUIKitCommunity" = "Community"; "TUIKitContactsNewFriends" = "New Contacts"; -"TUIKitContactsGroupChats" = "Group Chat"; +"TUIKitContactsGroupChats" = "Group Chats"; "TUIKitContactsBlackList" = "Blocked List"; "TUIKitAddFriendSourceFormat" = "Source: %@"; "TUIKitFriendApplicationApproved" = "Friend request accepted"; @@ -506,87 +505,105 @@ "Revoke" = "Revoke"; "Resend" = "Resend"; "Search" = "Search"; -"TUIKitCallMicCamAuthTips" = "Please granted the prillage to mic and camera"; -"TUIKitCallMicAuthTips" = "Please granted the prillage to mic"; -"TUIKitMessageTipsSureToResend" = "Sure to re-send message?"; +"TUIKitCallMicCamAuthTips" = "Allow access to your microphone and camera"; +"TUIKitCallMicAuthTips" = "Allow access to your microphone"; +"TUIKitMessageTipsSureToResend" = "Are you sure you want to resend the message?"; // 消息转发 -"TUIKitRelayNoMessageTips" = "Please select one message at least"; -"TUIKitRelayRecentMessages" = "Recent messages"; -"TUIKitRelayChatHistory" = "History"; -"TUIKitRelaySepcialForbid" = "Audio messages and other special messages cannot be forwarded."; -"TUIKitRelayConfirmForward" = "Forward to?"; -"TUIKitRelayOneByOneForward" = "One-by-one forward"; -"TUIKitRelayCombineForwad" = "Combine and forward"; -"TUIKitRelayGroupChatHistory" = "Chat history for group"; -"TUIKitRelayChatHistoryForSomebodyFormat" = "Chat history for %@ and %@"; -"TUIKitRelayErrorForwardFormat" = "@\"Forward error, code:%d, desc:%@\""; -"TUIKitRelayTargetCreateNewChat" = "Create new chat"; -"TUIKitRelayTargetSelectFromContacts" = "Select from Contacts"; -"TUIKitRelayTargetCrateGroupError" = "Create group error"; -"TUIKitRelayTargetNoneTips" = "Contact or conversation cannot null"; -"TUIKitRelayLayerLimitTips" = "Sorry,the message has over-limit layers"; -"TUIKitRelayCompatibleText" = "Unsupported merged message, please upgrade to the latest version."; -"TUIKitRelayUnsupportForward" = "Messages that failed to be sent do not support forwarding"; -"TUIKitRelayOneByOnyOverLimit" = "Too many forwarded messages, one by one forwarding is not currently supported"; +"TUIKitRelayNoMessageTips" = "Select messages"; +"TUIKitRelayRecentMessages" = "Recent Chats"; +"TUIKitRelayChatHistory" = "Chat History"; +"TUIKitRelaySepcialForbid" = "Cannot forward some selected messages (e.g., voice messages)."; +"TUIKitRelayConfirmForward" = "Are you sure you want to forward the messages?"; +"TUIKitRelayOneByOneForward" = "One-by-One Forward"; +"TUIKitRelayCombineForwad" = "Combine and Forward"; +"TUIKitRelayGroupChatHistory" = "Group Chat History"; +"TUIKitRelayChatHistoryForSomebodyFormat" = "Chat History of %@ and %@"; +"TUIKitRelayErrorForwardFormat" = "@\"Failed to forward: code:%d, desc:%@\""; +"TUIKitRelayTargetCreateNewChat" = "New Chat"; +"TUIKitRelayTargetSelectFromContacts" = "Select from contacts"; +"TUIKitRelayTargetCrateGroupError" = "Failed to create the group."; +"TUIKitRelayTargetNoneTips" = "Select a contact or conversation"; +"TUIKitRelayLayerLimitTips" = "The current nesting level exceeds the limit."; +"TUIKitRelayCompatibleText" = "Combine and Forward is not available. Upgrade to the latest version first."; +"TUIKitRelayUnsupportForward" = "Unable to forward failed messages."; +"TUIKitRelayOneByOnyOverLimit" = "Unable to forward the messages one by one because there are too many messages."; // 消息搜索 -"TUIKitSearchItemHeaderTitleContact" = "Contact"; -"TUIKitSearchItemFooterTitleContact" = "More contacts"; -"TUIKitSearchItemHeaderTitleGroup" = "Group"; -"TUIKitSearchItemFooterTitleGroup" = "More Groups"; -"TUIkitSearchItemHeaderTitleChatHistory" = "Chat history"; -"TUIKitSearchItemFooterTitleChatHistory" = "More chat history"; -"TUIKitSearchResultMatchFormat" = "Included:%@"; -"TUIKitSearchResultMatchGroupIDFormat" = "Inclued group ID:%@"; -"TUIKitSearchResultMatchGroupMember" = "Inclued group member:"; -"TUIKitSearchResultDisplayChatHistoryCountFormat" = "%zd of chat history"; +"TUIKitSearchItemHeaderTitleContact" = "Contacts"; +"TUIKitSearchItemFooterTitleContact" = "More"; +"TUIKitSearchItemHeaderTitleGroup" = "Group Chat"; +"TUIKitSearchItemFooterTitleGroup" = "More"; +"TUIkitSearchItemHeaderTitleChatHistory" = "Chat History"; +"TUIKitSearchItemFooterTitleChatHistory" = "More"; +"TUIKitSearchResultMatchFormat" = "Contains: %@"; +"TUIKitSearchResultMatchGroupIDFormat" = "Contains group ID: %@"; +"TUIKitSearchResultMatchGroupMember" = "Contains group members:"; +"TUIKitSearchResultDisplayChatHistoryCountFormat" = "%zd message(s)"; /***************************** 消息转发 & 消息搜索 *************************/ /***************************** 消息回复 & 换肤 *************************/ "Reply" = "Reply"; -"TUIKitReplyMessageNotFoundOriginMessage" = "Unable to locate the original message"; -"TUIKitClearAllChatHistory" = "Clear chat history"; -"TUIKitClearAllChatHistoryTips" = "Sure to clear all chat history?"; -"Discline" = "Discline"; +"TUIKitReplyMessageNotFoundOriginMessage" = "Unable to locate the original message."; +"TUIKitClearAllChatHistory" = "Clear Chat History"; +"TUIKitClearAllChatHistoryTips" = "Are you sure you want to clear the chat history?"; +"Discline" = "Decline"; "Copied" = "Copied"; -"ConfirmDeleteMessage" = "Sure to delete selected messages"; -"TUIKitAddFriend" = "Add friend"; +"ConfirmDeleteMessage" = "Delete now"; +"TUIKitAddFriend" = "Add Contact"; "TUIKitAddGroup" = "Add Group"; -"TUIKitSearchUserID" = "Search user ID"; -"TUIKitSearchGroupID" = "Search group ID"; -"TUIKitNoSelfSignature" = "What's Up not set"; -"TUIKitSelfSignatureFormat" = "Signature:%@"; -"TUIKitGroupProfileManage" = "Group manage"; -"TUIKitGroupManageShutupAllTips" = "After the prohibition is opened, only group leaders and administrators are allowed to speak."; -"TUIKitGroupManageAdminSetting" = "Setting administrators"; -"TUIKitGroupManageShutAll" = "Blocked from posting"; -"TUIKitGroupAddShutupMember" = "Add Member of blocked from posting"; -"TUIKitGroupOwner" = "Group owner"; -"TUIKitGroupManagerFormat" = "Group manager(%zd/%zd)"; -"TUIKitSetShutupAllFormat" = "%@ mute all"; -"TUIKitCancelShutupAllFormat" = "%@ cancel mute all"; -"TUIKitGroupNotice" = "Group notice"; -"TUIKitGroupNoticeNull" = "Group notice not set"; +"TUIKitSearchUserID" = "Search by user ID"; +"TUIKitSearchGroupID" = "Search by group ID"; +"TUIKitNoSelfSignature" = "No status"; +"TUIKitSelfSignatureFormat" = "Status: %@"; +"TUIKitGroupProfileManage" = "Manage"; +"TUIKitGroupManageShutupAllTips" = "When Mute All is enabled, only the group owner and admins are allowed to send messages."; +"TUIKitGroupManageAdminSetting" = "Set as Admin"; +"TUIKitGroupManageShutAll" = "Mute All"; +"TUIKitGroupAddShutupMember" = "Add members to mute"; +"TUIKitGroupOwner" = "Group Owner"; +"TUIKitGroupManagerFormat" = "Admin (%zd/%zd)"; +"TUIKitSetShutupAllFormat" = "%@ enabled Mute All"; +"TUIKitCancelShutupAllFormat" = "%@ disabled Mute All"; +"TUIKitGroupNotice" = "Group Notice"; +"TUIKitGroupNoticeNull" = "No group notice"; "Edit" = "Edit"; -"Done" = "Done"; -"TUIKitAddContactMyUserIDFormat" = "My identifier: %@"; -"TUIKitAddUserNoDataTips" = "The user not exists"; -"TUIKitAddGroupNoDataTips" = "The group not exists"; +"Done" = "OK"; +"TUIKitAddContactMyUserIDFormat" = "My User ID: %@"; +"TUIKitAddUserNoDataTips" = "This user does not exist."; +"TUIKitAddGroupNoDataTips" = "This group chat does not exist."; /***************************** 消息回复 & 换肤 *************************/ + /***************************** 视频、图片加载 & 保存 *************************/ -"TUIKitVideoTranscoding" = "video transcoding..."; -"TUIKitVideoDownloading" = "video downloading..."; -"TUIKitVideoSavedSuccess" = "video saved successfully"; -"TUIKitVideoSavedFailed" = "video save failed"; -"TUIKitPictureSavedSuccess" = "picture saved successfully"; -"TUIKitPictureSavedFailed" = "picture saved failed"; +"TUIKitVideoTranscoding" = "Transcoding video..."; +"TUIKitVideoDownloading" = "Downloading video..."; +"TUIKitVideoSavedSuccess" = "Video saved successfully."; +"TUIKitVideoSavedFailed" = "Failed to save video."; +"TUIKitPictureSavedSuccess" = "Image saved successfully."; +"TUIKitPictureSavedFailed" = "Failed to save image."; /***************************** 视频、图片加载 & 保存 *************************/ -"TUIKitGroupSetAdminsForbidden" = "The current type of group chat does not support setting administrators"; -"TUIKitGroupAddAdmins" = "Add administrators"; -"TUIKitGroupAddMemberOfBlockedForbidden" = "The current group chat does not support mute specified group members"; +"TUIKitGroupSetAdminsForbidden" = "Unable to set an admin for this type of group chat."; +"TUIKitGroupAddAdmins" = "Add Admin"; +"TUIKitGroupAddMemberOfBlockedForbidden" = "Unable to mute specified members for this type of group chat."; + "TUIKitNotDownload"= "Not download"; +"TUIKitInputNoCameraTitle" = "No access to camera"; +"TUIKitInputNoCameraTips" = "Unable to take photos, record videos, or make video calls. Click Authorize Now to allow access to your camera."; +"TUIKitInputNoCameraOperateLater" = "Later"; +"TUIKitInputNoCameraOperateEnable" = "Authorize Now"; + + +"TUIKitInputNoMicTitle" = "No access to microphone"; +"TUIKitInputNoMicTips" = "Unable to send voice messages, record videos, or make voice and video calls. Click Authorize Now to allow access to your microphone."; +"TUIKitInputNoMicOperateLater" = "Later"; +"TUIKitInputNoMicOperateEnable" = "Authorize Now"; + + +"TUIKitInputNoPhotoTitle" = "No access to images"; +"TUIKitInputNoPhotoTips" = "Unable to view or select images in a chat. Click Authorize Now to allow access to your images."; +"TUIKitInputNoPhotoOperateLater" = "Later"; +"TUIKitInputNoPhotoerateEnable" = "Authorize Now"; diff --git a/iOS/TUIKit/TUICore/Resources/TUIKitLocalizable.bundle/Localizable/zh-Hans.lproj/Localizable.strings b/iOS/TUIKit/TUICore/Resources/TUIKitLocalizable.bundle/Localizable/zh-Hans.lproj/Localizable.strings index bf9c9edd8..315230eca 100644 --- a/iOS/TUIKit/TUICore/Resources/TUIKitLocalizable.bundle/Localizable/zh-Hans.lproj/Localizable.strings +++ b/iOS/TUIKit/TUICore/Resources/TUIKitLocalizable.bundle/Localizable/zh-Hans.lproj/Localizable.strings @@ -6,6 +6,10 @@ */ +/***************************** 日期格式化 Start *************************/ +"YesterdayDateFormat" = "aHH:mm"; +/***************************** 日期格式化 End *************************/ + "TUIKitTipsMostSelectTextFormat" = "最多选择%ld个"; "Confirm" = "确定"; @@ -30,7 +34,6 @@ "Yesterday" = "昨天"; "am" = "上午"; "pm" = "下午"; -"YesterdayDateFormat" = "aHH:mm"; "Read" = "已读"; "Unread" = "未读"; @@ -68,10 +71,6 @@ "TUIKitInputHoldToTalk" = "按住 说话"; "TUIKitInputReleaseToSend" = "松开 结束"; "TUIKitInputReleaseToCancel" = "松开 取消"; -"TUIKitInputNoMicTitle" = "无法访问麦克风"; -"TUIKitInputNoMicTips" = "开启麦克风权限才能发送语音消息"; -"TUIKitInputNoMicOperateLater" = "以后再说"; -"TUIKitInputNoMicOperateEnable" = "去开启"; "TUIKitInputBlankMessageTitle" = "不能发送空白消息"; "TUIKitInputWillFinishRecordInSeconds" = "将在 %ld 秒后结束录制"; "TUIKitInputRecordSlideToCancel" = "手指上滑,取消发送"; @@ -591,3 +590,19 @@ "TUIKitGroupAddMemberOfBlockedForbidden" = "当前类型群聊暂不支持禁言指定群成员"; "TUIKitNotDownload"= "未下载"; + +"TUIKitInputNoCameraTitle" = "相机权限未开启"; +"TUIKitInputNoCameraTips" = "无法使用拍摄图片视频、视频通话等功能,点击\"去开启\"打开相机权限"; +"TUIKitInputNoCameraOperateLater" = "以后再说"; +"TUIKitInputNoCameraOperateEnable" = "去开启"; + +"TUIKitInputNoMicTitle" = "麦克风权限未开启"; +"TUIKitInputNoMicTips" = "无法使用发送语音消息、拍摄视频、音视频通话等功能,点击\"去开启\"打开麦克风权限"; +"TUIKitInputNoMicOperateLater" = "以后再说"; +"TUIKitInputNoMicOperateEnable" = "去开启"; + +"TUIKitInputNoPhotoTitle" = "图片权限未开启"; +"TUIKitInputNoPhotoTips" = "无法使用聊天功能中的图片查看、选择等功能,点击\"去开启\"打开图片权限"; +"TUIKitInputNoPhotoOperateLater" = "以后再说"; +"TUIKitInputNoPhotoerateEnable" = "去开启"; + diff --git a/iOS/TUIKit/TUICore/TUICommonModel.h b/iOS/TUIKit/TUICore/TUICommonModel.h index 9a212b74f..2b6a08b01 100755 --- a/iOS/TUIKit/TUICore/TUICommonModel.h +++ b/iOS/TUIKit/TUICore/TUICommonModel.h @@ -53,6 +53,7 @@ NS_ASSUME_NONNULL_BEGIN @interface TUICommonCellData : NSObject @property (strong) NSString *reuseId; @property (nonatomic, assign) SEL cselector; +@property (nonatomic, strong) NSDictionary *ext; - (CGFloat)heightOfWidth:(CGFloat)width; @end diff --git a/iOS/TUIKit/TUICore/TUIConfig.h b/iOS/TUIKit/TUICore/TUIConfig.h index 897e9f8d8..0c2af3a8c 100644 --- a/iOS/TUIKit/TUICore/TUIConfig.h +++ b/iOS/TUIKit/TUICore/TUIConfig.h @@ -72,5 +72,10 @@ typedef NS_ENUM(NSInteger, TUIKitAvatarType) { */ @property(nonatomic, assign) BOOL enableToast; +/** + * 是否开启自定义铃音(仅针对 Android 有效) + */ +@property(nonatomic, assign) BOOL enableCustomRing; + - (void)setSceneOptimizParams:(NSString *)path; @end diff --git a/iOS/TUIKit/TUICore/TUICore.podspec b/iOS/TUIKit/TUICore/TUICore.podspec deleted file mode 100644 index f199f629f..000000000 --- a/iOS/TUIKit/TUICore/TUICore.podspec +++ /dev/null @@ -1,27 +0,0 @@ -Pod::Spec.new do |spec| - spec.name = 'TUICore' - spec.version = '1.0.0' - spec.platform = :ios - spec.ios.deployment_target = '9.0' - spec.license = { :type => 'Proprietary', - :text => <<-LICENSE - copyright 2017 tencent Ltd. All rights reserved. - LICENSE - } - spec.homepage = 'https://cloud.tencent.com/document/product/269/3794' - spec.documentation_url = 'https://cloud.tencent.com/document/product/269/9147' - spec.authors = 'tencent video cloud' - spec.summary = 'TUICore' - spec.dependency 'TXIMSDK_Plus_iOS' - spec.dependency 'ReactiveObjC' - spec.dependency 'SDWebImage' - - spec.requires_arc = true - - spec.source = { :git => 'https://git.woa.com/lynxzhang/tui-components.git', :tag => spec.version} - - spec.source_files = '*.{h,m,mm,c}' - spec.resource = [ - 'Resources/*.bundle' - ] -end diff --git a/iOS/TUIKit/TUIGroup/Cell/CellUI/TUIMemberInfoCell.m b/iOS/TUIKit/TUIGroup/Cell/CellUI/TUIMemberInfoCell.m index 98b9da21e..6393dde85 100644 --- a/iOS/TUIKit/TUIGroup/Cell/CellUI/TUIMemberInfoCell.m +++ b/iOS/TUIKit/TUIGroup/Cell/CellUI/TUIMemberInfoCell.m @@ -40,7 +40,7 @@ { _data = data; - UIImage *defaultImage = [UIImage imageNamed:TUICoreImagePath(@"default_c2c_head")]; + UIImage *defaultImage = TUIConfig.defaultConfig.defaultAvatarImage; [self.avatarImageView sd_setImageWithURL:[NSURL URLWithString:data.avatarUrl] placeholderImage:data.avatar?:defaultImage]; self.nameLabel.text = data.name; diff --git a/iOS/TUIKit/TUIGroup/Cell/CellUI/TUISelectGroupMemberCell.m b/iOS/TUIKit/TUIGroup/Cell/CellUI/TUISelectGroupMemberCell.m index aa41d777d..53912f8fc 100644 --- a/iOS/TUIKit/TUIGroup/Cell/CellUI/TUISelectGroupMemberCell.m +++ b/iOS/TUIKit/TUIGroup/Cell/CellUI/TUISelectGroupMemberCell.m @@ -37,7 +37,7 @@ { _userModel = model; _selectedMark.image = isSelect ? [UIImage imageNamed:TUIGroupImagePath(@"ic_selected")] : [UIImage imageNamed:TUIGroupImagePath(@"ic_unselect")]; - [_userImg sd_setImageWithURL:[NSURL URLWithString:model.avatar] placeholderImage:[UIImage imageNamed:TUICoreImagePath(@"default_c2c_head")]]; + [_userImg sd_setImageWithURL:[NSURL URLWithString:model.avatar] placeholderImage:TUIConfig.defaultConfig.defaultAvatarImage]; _nameLabel.text = model.name; } diff --git a/iOS/TUIKit/TUIGroup/DataProvider/TUIGroupManageDataProvider.h b/iOS/TUIKit/TUIGroup/DataProvider/TUIGroupManageDataProvider.h index 5a7c751b3..78cadffaa 100644 --- a/iOS/TUIKit/TUIGroup/DataProvider/TUIGroupManageDataProvider.h +++ b/iOS/TUIKit/TUIGroup/DataProvider/TUIGroupManageDataProvider.h @@ -41,6 +41,7 @@ NS_ASSUME_NONNULL_BEGIN // 禁言/取消禁言 指定人员 - (void)mute:(BOOL)mute user:(TUIUserModel *)user; +- (void)updateMuteMembersFilterAdmins; @end NS_ASSUME_NONNULL_END diff --git a/iOS/TUIKit/TUIGroup/DataProvider/TUIGroupManageDataProvider.m b/iOS/TUIKit/TUIGroup/DataProvider/TUIGroupManageDataProvider.m index 6a2b57e54..252e8d3d9 100644 --- a/iOS/TUIKit/TUIGroup/DataProvider/TUIGroupManageDataProvider.m +++ b/iOS/TUIKit/TUIGroup/DataProvider/TUIGroupManageDataProvider.m @@ -179,7 +179,6 @@ member.avatarUrl = info.faceURL; member.name = (info.nameCard?:info.nickName)?:info.userID; member.identifier = info.userID; - BOOL exist = NO; for (TUIMemberInfoCellData *data in weakSelf.muteMembersDataArray) { if ([data.identifier isEqualToString:info.userID]) { @@ -188,7 +187,14 @@ } } - if (!exist) { + BOOL isSuper = (info.role == V2TIM_GROUP_MEMBER_ROLE_SUPER); + BOOL isAdMin = (info.role == V2TIM_GROUP_MEMBER_ROLE_ADMIN); + BOOL allowShowInMuteList = YES; + //在已禁言列表中需要过滤管理员 + if (isSuper || isAdMin) { + allowShowInMuteList = NO; + } + if (!exist && allowShowInMuteList) { [weakSelf.muteMembersDataArray addObject:member]; [indexPaths addObject:[NSIndexPath indexPathForRow:[weakSelf.muteMembersDataArray indexOfObject:member] inSection:1]]; } @@ -250,7 +256,9 @@ // } //} - +- (void)updateMuteMembersFilterAdmins { + [self loadMuteMembers]; +} - (NSMutableArray *)datas { if (_datas == nil) { diff --git a/iOS/TUIKit/TUIGroup/TUIGroup.podspec b/iOS/TUIKit/TUIGroup/TUIGroup.podspec deleted file mode 100644 index 8adc0dc16..000000000 --- a/iOS/TUIKit/TUIGroup/TUIGroup.podspec +++ /dev/null @@ -1,25 +0,0 @@ -Pod::Spec.new do |spec| - spec.name = 'TUIGroup' - spec.version = '1.0.0' - spec.platform = :ios - spec.ios.deployment_target = '9.0' - spec.license = { :type => 'Proprietary', - :text => <<-LICENSE - copyright 2017 tencent Ltd. All rights reserved. - LICENSE - } - spec.homepage = 'https://cloud.tencent.com/document/product/269/3794' - spec.documentation_url = 'https://cloud.tencent.com/document/product/269/9147' - spec.authors = 'tencent video cloud' - spec.summary = 'TUIGroup' - spec.dependency 'TUICore' - - spec.requires_arc = true - - spec.source = { :git => 'https://git.woa.com/lynxzhang/tui-components.git', :tag => spec.version} - spec.source_files = '**/*.{h,m,mm,c}' - - spec.resource = [ - 'Resources/*.bundle' - ] -end diff --git a/iOS/TUIKit/TUIGroup/UI/TUIGroupManageController.m b/iOS/TUIKit/TUIGroup/UI/TUIGroupManageController.m index d2c73e711..656c4232f 100644 --- a/iOS/TUIKit/TUIGroup/UI/TUIGroupManageController.m +++ b/iOS/TUIKit/TUIGroup/UI/TUIGroupManageController.m @@ -61,6 +61,11 @@ } TUISettingAdminController *vc = [[TUISettingAdminController alloc] init]; vc.groupID = self.groupID; + __weak typeof(self)weakSelf = self; + vc.settingAdminDissmissCallBack = ^{ + [weakSelf.dataProvider updateMuteMembersFilterAdmins]; + [weakSelf.tableView reloadData]; + }; [self.navigationController pushViewController:vc animated:YES]; } diff --git a/iOS/TUIKit/TUIGroup/UI/TUISettingAdminController.h b/iOS/TUIKit/TUIGroup/UI/TUISettingAdminController.h index 9f6733a1f..16fd78189 100644 --- a/iOS/TUIKit/TUIGroup/UI/TUISettingAdminController.h +++ b/iOS/TUIKit/TUIGroup/UI/TUISettingAdminController.h @@ -13,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, copy) NSString *groupID; +@property (nonatomic, copy) void (^settingAdminDissmissCallBack)(void); @end NS_ASSUME_NONNULL_END diff --git a/iOS/TUIKit/TUIGroup/UI/TUISettingAdminController.m b/iOS/TUIKit/TUIGroup/UI/TUISettingAdminController.m index 0cc6f4548..86ade3141 100644 --- a/iOS/TUIKit/TUIGroup/UI/TUISettingAdminController.m +++ b/iOS/TUIKit/TUIGroup/UI/TUISettingAdminController.m @@ -22,6 +22,11 @@ @implementation TUISettingAdminController +- (void)dealloc { + if (self.settingAdminDissmissCallBack) { + self.settingAdminDissmissCallBack(); + } +} - (void)viewDidLoad { [super viewDidLoad]; diff --git a/iOS/TUIKit/TUISearch/TUISearch.podspec b/iOS/TUIKit/TUISearch/TUISearch.podspec deleted file mode 100644 index 978514f93..000000000 --- a/iOS/TUIKit/TUISearch/TUISearch.podspec +++ /dev/null @@ -1,25 +0,0 @@ -Pod::Spec.new do |spec| - spec.name = 'TUISearch' - spec.version = '1.0.0' - spec.platform = :ios - spec.ios.deployment_target = '9.0' - spec.license = { :type => 'Proprietary', - :text => <<-LICENSE - copyright 2017 tencent Ltd. All rights reserved. - LICENSE - } - spec.homepage = 'https://cloud.tencent.com/document/product/269/3794' - spec.documentation_url = 'https://cloud.tencent.com/document/product/269/9147' - spec.authors = 'tencent video cloud' - spec.summary = 'TUISearch' - spec.dependency 'TUICore' - - spec.requires_arc = true - - spec.source = { :git => 'https://git.woa.com/lynxzhang/tui-components.git', :tag => spec.version} - spec.source_files = '**/*.{h,m,mm,c}' - - spec.resource = [ - 'Resources/*.bundle' - ] -end