├── .gitignore ├── EaseChatDemo ├── EMPushServerExt │ ├── EMPushExtension.framework │ │ ├── EMPushExtension │ │ ├── Headers │ │ │ ├── EMPushExtension.h │ │ │ └── EMPushServiceExt.h │ │ ├── Info.plist │ │ ├── Modules │ │ │ └── module.modulemap │ │ └── _CodeSignature │ │ │ ├── CodeDirectory │ │ │ ├── CodeRequirements │ │ │ ├── CodeRequirements-1 │ │ │ ├── CodeResources │ │ │ └── CodeSignature │ ├── Info.plist │ ├── NotificationService.swift │ └── PrivacyInfo.xcprivacy ├── EaseChatDemo.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── EMPushServerExt.xcscheme │ │ └── EaseChatDemo.xcscheme ├── EaseChatDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon-1024.png │ │ │ ├── icon-20@2x.png │ │ │ ├── icon-20@3x.png │ │ │ ├── icon-29@2x.png │ │ │ ├── icon-29@3x.png │ │ │ ├── icon-38@2x.png │ │ │ ├── icon-38@3x.png │ │ │ ├── icon-40@2x.png │ │ │ ├── icon-40@3x.png │ │ │ ├── icon-60@2x.png │ │ │ ├── icon-60@3x.png │ │ │ ├── icon-64@2x.png │ │ │ ├── icon-64@3x.png │ │ │ ├── icon-68@2x.png │ │ │ ├── icon-76@2x.png │ │ │ └── icon-83.5@2x.png │ │ ├── Contents.json │ │ ├── Main │ │ │ ├── Contents.json │ │ │ ├── close.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── close@2x.png │ │ │ │ └── close@3x.png │ │ │ ├── conversation_ondark.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── conversation_ondark@2x.png │ │ │ │ └── conversation_ondark@3x.png │ │ │ ├── conversation_onlight.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── conversation_onlight@2x.png │ │ │ │ └── conversation_onlight@3x.png │ │ │ ├── fraud_icon.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── fraud_icon@2x.png │ │ │ │ └── fraud_icon@3x.png │ │ │ ├── tabbar_chats.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── tabbar_chats@2x.png │ │ │ │ └── tabbar_chats@3x.png │ │ │ ├── tabbar_contacts.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── tabbar_contacts@2x.png │ │ │ │ └── tabbar_contacts@3x.png │ │ │ └── tabbar_mine.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── tabbar_mine@2x.png │ │ │ │ └── tabbar_mine@3x.png │ │ ├── Me │ │ │ ├── Contents.json │ │ │ ├── NoDisturb.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── NoDisturb@2x.png │ │ │ │ └── NoDisturb@3x.png │ │ │ ├── about.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── about@2x.png │ │ │ ├── appicon.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── appicon@2x.png │ │ │ │ └── appicon@3x.png │ │ │ ├── attmsg_style1_ondark.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── attmsg_style1_ondark@3x.png │ │ │ ├── attmsg_style1_onlight.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── attmsg_style1_onlight@3x.png │ │ │ ├── attmsg_style2_ondark.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── attmsg_style2_ondark@3x.png │ │ │ ├── attmsg_style2_onlight.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── attmsg_style2_onlight@3x.png │ │ │ ├── away.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── away@2x.png │ │ │ │ └── away@3x.png │ │ │ ├── check.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── check@2x.png │ │ │ │ └── check@3x.png │ │ │ ├── custom_status.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── cutom_status@2x.png │ │ │ │ └── cutom_status@3x.png │ │ │ ├── general.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── general@2x.png │ │ │ │ └── general@3x.png │ │ │ ├── msgmenu_style1_ondark.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── msgmenu_style1_ondark@3x.png │ │ │ ├── msgmenu_style1_onlight.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── msgmenu_style1_onlight@3x.png │ │ │ ├── msgmenu_style2_ondark.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── msgmenu_style2_ondark@3x.png │ │ │ ├── msgmenu_style2_onlight.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── msgmenu_style2_onlight@3x.png │ │ │ ├── notification.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── notification@2x.png │ │ │ │ └── notification@3x.png │ │ │ ├── online_status.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── online_status@2x.png │ │ │ │ └── online_status@3x.png │ │ │ ├── privacy.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── privacy@2x.png │ │ │ │ └── privacy@3x.png │ │ │ ├── uncheck.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── uncheck@2x.png │ │ │ │ └── uncheck@3x.png │ │ │ └── userinfo.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── userinfo@2x.png │ │ │ │ └── userinfo@3x.png │ │ ├── login │ │ │ ├── Contents.json │ │ │ ├── easemobchat.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── easemobchat@2x.png │ │ │ │ └── easemobchat@3x.png │ │ │ ├── launch_icon.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── launch_icon@2x.png │ │ │ │ └── launch_icon@3x.png │ │ │ ├── login_bg.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── roomList@2x.png │ │ │ │ └── roomList@3x.png │ │ │ ├── login_bg_dark.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── login_bg_dark@2x.png │ │ │ │ └── login_bg_dark@3x.png │ │ │ ├── selected.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── selected@2x.png │ │ │ │ └── selected@3x.png │ │ │ └── unselected.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── unselected@2x.png │ │ │ │ └── unselected@3x.png │ │ ├── phone_hang.imageset │ │ │ ├── Contents.json │ │ │ ├── phone_hang@2x.png │ │ │ └── phone_hang@3x.png │ │ └── video_call.imageset │ │ │ ├── Contents.json │ │ │ ├── video_call@2x.png │ │ │ └── video_call@3x.png │ ├── BalooTamma-Regular.ttf │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Config.plist │ ├── CustomConstants │ │ └── PublicDefines.swift │ ├── EaseChatDemo.entitlements │ ├── Info.plist │ ├── IntegratedFromEaseChatUIKit │ │ ├── Controllers │ │ │ ├── MineCallInviteUsersController.swift │ │ │ ├── MineContactDetailViewController.swift │ │ │ ├── MineContactRemarkEditViewController.swift │ │ │ ├── MineContactsViewController.swift │ │ │ ├── MineConversationsController.swift │ │ │ ├── MineGroupDetailViewController.swift │ │ │ └── MineMessageListViewController.swift │ │ ├── ViewModels │ │ │ └── MineMessageEntity.swift │ │ └── Views │ │ │ └── FraudAlertView.swift │ ├── LoginViewController.swift │ ├── Main │ │ └── MainViewController.swift │ ├── Me │ │ ├── Cells │ │ │ ├── AboutEasemobCell.swift │ │ │ ├── ColorHueSettingCell.swift │ │ │ ├── FeatureSwitchCell.swift │ │ │ ├── LanguageCell.swift │ │ │ ├── MeMenuCell.swift │ │ │ └── PersonalInfoCell.swift │ │ ├── Controllers │ │ │ ├── AboutEasemobController.swift │ │ │ ├── ColorSettingViewController.swift │ │ │ ├── FeatureSwitchViewController.swift │ │ │ ├── GeneralViewController.swift │ │ │ ├── LanguageSettingViewController.swift │ │ │ ├── MeViewController.swift │ │ │ ├── NotificationSettingViewController.swift │ │ │ ├── PersonalInfoViewController.swift │ │ │ ├── PrivacyPolicyViewController.swift │ │ │ ├── PrivacySettingViewController.swift │ │ │ ├── ShowMenuStyleViewController.swift │ │ │ ├── ThemesSettingViewController.swift │ │ │ └── TranslateLanguageSettingController.swift │ │ └── Views │ │ │ ├── AboutEasemobHeader.swift │ │ │ └── GradientSlider.swift │ ├── PhotoBrowser │ │ ├── ImagePreviewCell.swift │ │ ├── ImagePreviewController.swift │ │ ├── ImagePreviewTransitionDelegate.swift │ │ └── PreviewImage.swift │ ├── SceneDelegate.swift │ ├── ServerConfigViewController.swift │ ├── Utils │ │ ├── DemoLanguage.swift │ │ ├── EaseMobHUD.swift │ │ ├── EasemobBusinessApi.swift │ │ ├── EasemobBusinessRequest.swift │ │ ├── EasemobRequest.swift │ │ ├── GCDTimer.swift │ │ └── PresenceManager.swift │ ├── en.lproj │ │ └── Localizable.strings │ └── zh-Hans.lproj │ │ ├── LaunchScreen.strings │ │ ├── Localizable.strings │ │ └── Main.strings └── Podfile ├── README.md └── demo.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Config File 34 | EaseChatDemo/EaseChatDemo/Config.plist 35 | 36 | # CocoaPods 37 | # 38 | # We recommend against adding the Pods directory to your .gitignore. However 39 | # you should judge for yourself, the pros and cons are mentioned at: 40 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 41 | # 42 | Pods/ 43 | # 44 | # Add this line if you want to avoid checking in source code from the Xcode workspace 45 | *.xcworkspace 46 | 47 | # Carthage 48 | # 49 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 50 | # Carthage/Checkouts 51 | 52 | Carthage/Build/ 53 | 54 | # fastlane 55 | # 56 | # It is recommended to not store the screenshots in the git repo. 57 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 58 | # For more information about the recommended setup visit: 59 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 60 | 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots/**/*.png 64 | fastlane/test_output 65 | Products/ 66 | # Code Injection 67 | # 68 | # After new code Injection tools there's a generated folder /iOSInjectionProject 69 | # https://github.com/johnno1962/injectionforxcode 70 | 71 | iOSInjectionProject/ 72 | .DS_Store 73 | Podfile.lock 74 | EaseChatDemo/EaseChatDemo/CustomConstants/PublicDefines.swift 75 | -------------------------------------------------------------------------------- /EaseChatDemo/EMPushServerExt/EMPushExtension.framework/EMPushExtension: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EMPushServerExt/EMPushExtension.framework/EMPushExtension -------------------------------------------------------------------------------- /EaseChatDemo/EMPushServerExt/EMPushExtension.framework/Headers/EMPushExtension.h: -------------------------------------------------------------------------------- 1 | // 2 | // EMPushExtension.h 3 | // EMPushExtension 4 | // 5 | // Created by hxq on 2022/2/21. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for EMPushExtension. 11 | FOUNDATION_EXPORT double EMPushExtensionVersionNumber; 12 | 13 | //! Project version string for EMPushExtension. 14 | FOUNDATION_EXPORT const unsigned char EMPushExtensionVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | #import 18 | -------------------------------------------------------------------------------- /EaseChatDemo/EMPushServerExt/EMPushExtension.framework/Headers/EMPushServiceExt.h: -------------------------------------------------------------------------------- 1 | // 2 | // EMPushServiceExt.h 3 | // EMPushServiceExt 4 | // 5 | // Created by hxq on 2022/2/16. 6 | // Copyright © 2022 easemob.com. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface EMPushServiceExt : NSObject 13 | /*! 14 | * \~chinese 15 | * 设置appkey(需要与main target中的appkey相同) 16 | * 17 | * @param aAppkey app唯一标识符 18 | * 19 | * \~english 20 | * Set appKey (needs to be the same as appKey in main Target) 21 | * 22 | * @param aAppkey Application's unique identifier 23 | * 24 | */ 25 | + (void)setAppkey:(NSString *)aAppkey; 26 | 27 | /*! 28 | * \~chinese 29 | * APNS推送送达上报 30 | * 31 | * @param aRequest apns请求 32 | * @param aCompletionBlock 完成的回调 33 | * 34 | * \~english 35 | * 36 | * APNS push delivered and reported 37 | * 38 | * @param aRequest apns request 39 | * @param aCompletionBlock The callback of completion block 40 | */ 41 | + (void)receiveRemoteNotificationRequest:(UNNotificationRequest *)aRequest completion:(void (^)(NSError*error))aCompletionBlock; 42 | @end 43 | 44 | -------------------------------------------------------------------------------- /EaseChatDemo/EMPushServerExt/EMPushExtension.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EMPushServerExt/EMPushExtension.framework/Info.plist -------------------------------------------------------------------------------- /EaseChatDemo/EMPushServerExt/EMPushExtension.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module EMPushExtension { 2 | umbrella header "EMPushExtension.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /EaseChatDemo/EMPushServerExt/EMPushExtension.framework/_CodeSignature/CodeDirectory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EMPushServerExt/EMPushExtension.framework/_CodeSignature/CodeDirectory -------------------------------------------------------------------------------- /EaseChatDemo/EMPushServerExt/EMPushExtension.framework/_CodeSignature/CodeRequirements: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EMPushServerExt/EMPushExtension.framework/_CodeSignature/CodeRequirements -------------------------------------------------------------------------------- /EaseChatDemo/EMPushServerExt/EMPushExtension.framework/_CodeSignature/CodeRequirements-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EMPushServerExt/EMPushExtension.framework/_CodeSignature/CodeRequirements-1 -------------------------------------------------------------------------------- /EaseChatDemo/EMPushServerExt/EMPushExtension.framework/_CodeSignature/CodeResources: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | files 6 | 7 | Headers/EMPushExtension.h 8 | 9 | gLrfoOuaFSzcw47xbqo8/ECfwu8= 10 | 11 | Headers/EMPushServiceExt.h 12 | 13 | hm9kSF0iLmv34WOfYHgKgnxOPQA= 14 | 15 | Info.plist 16 | 17 | fkDButDmtltYnV6se/P1pl9l4iA= 18 | 19 | Modules/module.modulemap 20 | 21 | qeP56fDDoIV+qPbBbMNoHXOzikc= 22 | 23 | 24 | files2 25 | 26 | Headers/EMPushExtension.h 27 | 28 | hash 29 | 30 | gLrfoOuaFSzcw47xbqo8/ECfwu8= 31 | 32 | hash2 33 | 34 | LPRy9CwV97aaUmKt3RqKf7T7Tl0YcWhscYf4oJkXnxk= 35 | 36 | 37 | Headers/EMPushServiceExt.h 38 | 39 | hash 40 | 41 | hm9kSF0iLmv34WOfYHgKgnxOPQA= 42 | 43 | hash2 44 | 45 | //KhmhCBJmYnIfO6HFfT/9SLOJ9UbCamBTYNx9sKOCk= 46 | 47 | 48 | Modules/module.modulemap 49 | 50 | hash 51 | 52 | qeP56fDDoIV+qPbBbMNoHXOzikc= 53 | 54 | hash2 55 | 56 | O+w3LCjFTo8nFCQIY0byPoHxTVSJJUin8c7uWakhPm8= 57 | 58 | 59 | 60 | rules 61 | 62 | ^.* 63 | 64 | ^.*\.lproj/ 65 | 66 | optional 67 | 68 | weight 69 | 1000 70 | 71 | ^.*\.lproj/locversion.plist$ 72 | 73 | omit 74 | 75 | weight 76 | 1100 77 | 78 | ^Base\.lproj/ 79 | 80 | weight 81 | 1010 82 | 83 | ^version.plist$ 84 | 85 | 86 | rules2 87 | 88 | .*\.dSYM($|/) 89 | 90 | weight 91 | 11 92 | 93 | ^(.*/)?\.DS_Store$ 94 | 95 | omit 96 | 97 | weight 98 | 2000 99 | 100 | ^.* 101 | 102 | ^.*\.lproj/ 103 | 104 | optional 105 | 106 | weight 107 | 1000 108 | 109 | ^.*\.lproj/locversion.plist$ 110 | 111 | omit 112 | 113 | weight 114 | 1100 115 | 116 | ^Base\.lproj/ 117 | 118 | weight 119 | 1010 120 | 121 | ^Info\.plist$ 122 | 123 | omit 124 | 125 | weight 126 | 20 127 | 128 | ^PkgInfo$ 129 | 130 | omit 131 | 132 | weight 133 | 20 134 | 135 | ^embedded\.provisionprofile$ 136 | 137 | weight 138 | 20 139 | 140 | ^version\.plist$ 141 | 142 | weight 143 | 20 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /EaseChatDemo/EMPushServerExt/EMPushExtension.framework/_CodeSignature/CodeSignature: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EMPushServerExt/EMPushExtension.framework/_CodeSignature/CodeSignature -------------------------------------------------------------------------------- /EaseChatDemo/EMPushServerExt/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.usernotifications.service 9 | NSExtensionPrincipalClass 10 | $(PRODUCT_MODULE_NAME).NotificationService 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /EaseChatDemo/EMPushServerExt/NotificationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationService.swift 3 | // EMPushServerExt 4 | // 5 | // Created by 朱继超 on 2024/3/18. 6 | // 7 | 8 | import UserNotifications 9 | import EMPushExtension 10 | 11 | class NotificationService: UNNotificationServiceExtension { 12 | 13 | var contentHandler: ((UNNotificationContent) -> Void)? 14 | var bestAttemptContent: UNMutableNotificationContent? 15 | 16 | override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { 17 | self.contentHandler = contentHandler 18 | bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) 19 | 20 | 21 | EMPushServiceExt.setAppkey("easemob#easeim") 22 | EMPushServiceExt.receiveRemoteNotificationRequest(request) { [weak self] error in 23 | if error == nil { 24 | debugPrint("EMPushServiceExt complete apns delivery") 25 | } else { 26 | debugPrint("EMPushServiceExt complete apns delivery error: \(error?.localizedDescription ?? "")") 27 | } 28 | if let bestAttemptContent = self?.bestAttemptContent { 29 | // Modify the notification content here... 30 | bestAttemptContent.title = "\(bestAttemptContent.title) [modified]" 31 | 32 | contentHandler(bestAttemptContent) 33 | } 34 | } 35 | 36 | } 37 | 38 | override func serviceExtensionTimeWillExpire() { 39 | // Called just before the extension will be terminated by the system. 40 | // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. 41 | if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { 42 | contentHandler(bestAttemptContent) 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /EaseChatDemo/EMPushServerExt/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPITypeReasons 9 | 10 | CA92.1 11 | 12 | NSPrivacyAccessedAPIType 13 | NSPrivacyAccessedAPICategoryUserDefaults 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo.xcodeproj/xcshareddata/xcschemes/EMPushServerExt.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 11 | 17 | 23 | 24 | 25 | 31 | 37 | 38 | 39 | 40 | 41 | 47 | 48 | 60 | 62 | 68 | 69 | 70 | 71 | 79 | 81 | 87 | 88 | 89 | 90 | 92 | 93 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo.xcodeproj/xcshareddata/xcschemes/EaseChatDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 45 | 47 | 53 | 54 | 55 | 56 | 62 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon-20@2x.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "scale" : "2x", 8 | "size" : "20x20" 9 | }, 10 | { 11 | "filename" : "icon-20@3x.png", 12 | "idiom" : "universal", 13 | "platform" : "ios", 14 | "scale" : "3x", 15 | "size" : "20x20" 16 | }, 17 | { 18 | "filename" : "icon-29@2x.png", 19 | "idiom" : "universal", 20 | "platform" : "ios", 21 | "scale" : "2x", 22 | "size" : "29x29" 23 | }, 24 | { 25 | "filename" : "icon-29@3x.png", 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "scale" : "3x", 29 | "size" : "29x29" 30 | }, 31 | { 32 | "filename" : "icon-38@2x.png", 33 | "idiom" : "universal", 34 | "platform" : "ios", 35 | "scale" : "2x", 36 | "size" : "38x38" 37 | }, 38 | { 39 | "filename" : "icon-38@3x.png", 40 | "idiom" : "universal", 41 | "platform" : "ios", 42 | "scale" : "3x", 43 | "size" : "38x38" 44 | }, 45 | { 46 | "filename" : "icon-40@2x.png", 47 | "idiom" : "universal", 48 | "platform" : "ios", 49 | "scale" : "2x", 50 | "size" : "40x40" 51 | }, 52 | { 53 | "filename" : "icon-40@3x.png", 54 | "idiom" : "universal", 55 | "platform" : "ios", 56 | "scale" : "3x", 57 | "size" : "40x40" 58 | }, 59 | { 60 | "filename" : "icon-60@2x.png", 61 | "idiom" : "universal", 62 | "platform" : "ios", 63 | "scale" : "2x", 64 | "size" : "60x60" 65 | }, 66 | { 67 | "filename" : "icon-60@3x.png", 68 | "idiom" : "universal", 69 | "platform" : "ios", 70 | "scale" : "3x", 71 | "size" : "60x60" 72 | }, 73 | { 74 | "filename" : "icon-64@2x.png", 75 | "idiom" : "universal", 76 | "platform" : "ios", 77 | "scale" : "2x", 78 | "size" : "64x64" 79 | }, 80 | { 81 | "filename" : "icon-64@3x.png", 82 | "idiom" : "universal", 83 | "platform" : "ios", 84 | "scale" : "3x", 85 | "size" : "64x64" 86 | }, 87 | { 88 | "filename" : "icon-68@2x.png", 89 | "idiom" : "universal", 90 | "platform" : "ios", 91 | "scale" : "2x", 92 | "size" : "68x68" 93 | }, 94 | { 95 | "filename" : "icon-76@2x.png", 96 | "idiom" : "universal", 97 | "platform" : "ios", 98 | "scale" : "2x", 99 | "size" : "76x76" 100 | }, 101 | { 102 | "filename" : "icon-83.5@2x.png", 103 | "idiom" : "universal", 104 | "platform" : "ios", 105 | "scale" : "2x", 106 | "size" : "83.5x83.5" 107 | }, 108 | { 109 | "filename" : "icon-1024.png", 110 | "idiom" : "universal", 111 | "platform" : "ios", 112 | "scale" : "1x", 113 | "size" : "1024x1024" 114 | } 115 | ], 116 | "info" : { 117 | "author" : "xcode", 118 | "version" : 1 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-38@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-38@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-38@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-38@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-64@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-64@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-64@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-64@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-68@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-68@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "close@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "close@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/close.imageset/close@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/close.imageset/close@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/close.imageset/close@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/close.imageset/close@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/conversation_ondark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "conversation_ondark@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "conversation_ondark@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/conversation_ondark.imageset/conversation_ondark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/conversation_ondark.imageset/conversation_ondark@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/conversation_ondark.imageset/conversation_ondark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/conversation_ondark.imageset/conversation_ondark@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/conversation_onlight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "conversation_onlight@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "conversation_onlight@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/conversation_onlight.imageset/conversation_onlight@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/conversation_onlight.imageset/conversation_onlight@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/conversation_onlight.imageset/conversation_onlight@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/conversation_onlight.imageset/conversation_onlight@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/fraud_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "fraud_icon@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "fraud_icon@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/fraud_icon.imageset/fraud_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/fraud_icon.imageset/fraud_icon@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/fraud_icon.imageset/fraud_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/fraud_icon.imageset/fraud_icon@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_chats.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "tabbar_chats@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "tabbar_chats@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_chats.imageset/tabbar_chats@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_chats.imageset/tabbar_chats@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_chats.imageset/tabbar_chats@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_chats.imageset/tabbar_chats@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_contacts.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "tabbar_contacts@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "tabbar_contacts@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_contacts.imageset/tabbar_contacts@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_contacts.imageset/tabbar_contacts@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_contacts.imageset/tabbar_contacts@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_contacts.imageset/tabbar_contacts@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_mine.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "tabbar_mine@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "tabbar_mine@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_mine.imageset/tabbar_mine@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_mine.imageset/tabbar_mine@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_mine.imageset/tabbar_mine@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Main/tabbar_mine.imageset/tabbar_mine@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/NoDisturb.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "NoDisturb@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "NoDisturb@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/NoDisturb.imageset/NoDisturb@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/NoDisturb.imageset/NoDisturb@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/NoDisturb.imageset/NoDisturb@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/NoDisturb.imageset/NoDisturb@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/about.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "about@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/about.imageset/about@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/about.imageset/about@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/appicon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "appicon@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "appicon@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/appicon.imageset/appicon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/appicon.imageset/appicon@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/appicon.imageset/appicon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/appicon.imageset/appicon@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/attmsg_style1_ondark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "attmsg_style1_ondark@3x.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/attmsg_style1_ondark.imageset/attmsg_style1_ondark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/attmsg_style1_ondark.imageset/attmsg_style1_ondark@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/attmsg_style1_onlight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "attmsg_style1_onlight@3x.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/attmsg_style1_onlight.imageset/attmsg_style1_onlight@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/attmsg_style1_onlight.imageset/attmsg_style1_onlight@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/attmsg_style2_ondark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "attmsg_style2_ondark@3x.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/attmsg_style2_ondark.imageset/attmsg_style2_ondark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/attmsg_style2_ondark.imageset/attmsg_style2_ondark@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/attmsg_style2_onlight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "attmsg_style2_onlight@3x.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/attmsg_style2_onlight.imageset/attmsg_style2_onlight@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/attmsg_style2_onlight.imageset/attmsg_style2_onlight@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/away.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "away@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "away@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/away.imageset/away@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/away.imageset/away@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/away.imageset/away@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/away.imageset/away@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/check.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "check@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "check@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/check.imageset/check@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/check.imageset/check@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/check.imageset/check@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/check.imageset/check@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/custom_status.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "cutom_status@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "cutom_status@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/custom_status.imageset/cutom_status@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/custom_status.imageset/cutom_status@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/custom_status.imageset/cutom_status@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/custom_status.imageset/cutom_status@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/general.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "general@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "general@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/general.imageset/general@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/general.imageset/general@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/general.imageset/general@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/general.imageset/general@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/msgmenu_style1_ondark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "msgmenu_style1_ondark@3x.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/msgmenu_style1_ondark.imageset/msgmenu_style1_ondark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/msgmenu_style1_ondark.imageset/msgmenu_style1_ondark@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/msgmenu_style1_onlight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "msgmenu_style1_onlight@3x.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/msgmenu_style1_onlight.imageset/msgmenu_style1_onlight@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/msgmenu_style1_onlight.imageset/msgmenu_style1_onlight@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/msgmenu_style2_ondark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "msgmenu_style2_ondark@3x.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/msgmenu_style2_ondark.imageset/msgmenu_style2_ondark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/msgmenu_style2_ondark.imageset/msgmenu_style2_ondark@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/msgmenu_style2_onlight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "msgmenu_style2_onlight@3x.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/msgmenu_style2_onlight.imageset/msgmenu_style2_onlight@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/msgmenu_style2_onlight.imageset/msgmenu_style2_onlight@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/notification.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "notification@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "notification@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/notification.imageset/notification@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/notification.imageset/notification@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/notification.imageset/notification@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/notification.imageset/notification@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/online_status.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "online_status@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "online_status@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/online_status.imageset/online_status@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/online_status.imageset/online_status@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/online_status.imageset/online_status@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/online_status.imageset/online_status@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/privacy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "privacy@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "privacy@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/privacy.imageset/privacy@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/privacy.imageset/privacy@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/privacy.imageset/privacy@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/privacy.imageset/privacy@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/uncheck.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "uncheck@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "uncheck@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/uncheck.imageset/uncheck@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/uncheck.imageset/uncheck@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/uncheck.imageset/uncheck@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/uncheck.imageset/uncheck@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/userinfo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "userinfo@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "userinfo@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/userinfo.imageset/userinfo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/userinfo.imageset/userinfo@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/userinfo.imageset/userinfo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/Me/userinfo.imageset/userinfo@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/easemobchat.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "easemobchat@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "easemobchat@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/easemobchat.imageset/easemobchat@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/login/easemobchat.imageset/easemobchat@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/easemobchat.imageset/easemobchat@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/login/easemobchat.imageset/easemobchat@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/launch_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "launch_icon@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "launch_icon@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/launch_icon.imageset/launch_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/login/launch_icon.imageset/launch_icon@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/launch_icon.imageset/launch_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/login/launch_icon.imageset/launch_icon@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/login_bg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "roomList@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "roomList@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/login_bg.imageset/roomList@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/login/login_bg.imageset/roomList@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/login_bg.imageset/roomList@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/login/login_bg.imageset/roomList@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/login_bg_dark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "login_bg_dark@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "login_bg_dark@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/login_bg_dark.imageset/login_bg_dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/login/login_bg_dark.imageset/login_bg_dark@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/login_bg_dark.imageset/login_bg_dark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/login/login_bg_dark.imageset/login_bg_dark@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "selected@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "selected@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/selected.imageset/selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/login/selected.imageset/selected@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/selected.imageset/selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/login/selected.imageset/selected@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/unselected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "unselected@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "unselected@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/unselected.imageset/unselected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/login/unselected.imageset/unselected@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/login/unselected.imageset/unselected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/login/unselected.imageset/unselected@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/phone_hang.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "phone_hang@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "phone_hang@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/phone_hang.imageset/phone_hang@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/phone_hang.imageset/phone_hang@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/phone_hang.imageset/phone_hang@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/phone_hang.imageset/phone_hang@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/video_call.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "video_call@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "video_call@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/video_call.imageset/video_call@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/video_call.imageset/video_call@2x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Assets.xcassets/video_call.imageset/video_call@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/Assets.xcassets/video_call.imageset/video_call@3x.png -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/BalooTamma-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/EaseChatDemo/EaseChatDemo/BalooTamma-Regular.ttf -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Config.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AES_KEY 6 | 7 | SMS_URL 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/CustomConstants/PublicDefines.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PublicFiles.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/5. 6 | // 7 | 8 | import Foundation 9 | 10 | //参考环信官方文档 11 | public let AppKey: String = <#Your AppKey#> 12 | //最好使用自己的Appserver地址,尽量不要使用此地址,此地址只用于快速跑通Demo,此Demo对应Appserver是开源的具体参看demo readme 13 | public let ServerHost: String = <#Your Appserver Address#> 14 | 15 | // https://docs.agora.io/en/agora-chat/get-started/enable?platform=ios 16 | // Create a new app in the Agora Console, and use the App Id in the following code 17 | public let CallKitAppId: String = <#Your Agora App Id#> 18 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/EaseChatDemo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.icloud-container-identifiers 8 | 9 | iCloud.com.easemob.easeim 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudDocuments 14 | 15 | com.apple.developer.ubiquity-container-identifiers 16 | 17 | iCloud.com.easemob.easeim 18 | 19 | com.apple.developer.ubiquity-kvstore-identifier 20 | $(TeamIdentifierPrefix)$(CFBundleIdentifier) 21 | 22 | 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIAppFonts 6 | 7 | BalooTamma-Regular.ttf 8 | 9 | LSApplicationQueriesSchemes 10 | 11 | tel 12 | 13 | Privacy - File Access Usage Description 14 | 环信想使用您的文件用于发送文件消息 15 | UIApplicationSceneManifest 16 | 17 | UIApplicationSupportsMultipleScenes 18 | 19 | UISceneConfigurations 20 | 21 | UIWindowSceneSessionRoleApplication 22 | 23 | 24 | UISceneConfigurationName 25 | Default Configuration 26 | UISceneDelegateClassName 27 | $(PRODUCT_MODULE_NAME).SceneDelegate 28 | UISceneStoryboardFile 29 | Main 30 | 31 | 32 | 33 | 34 | UIBackgroundModes 35 | 36 | audio 37 | remote-notification 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/IntegratedFromEaseChatUIKit/Controllers/MineCallInviteUsersController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MineCallInviteUsersController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/14. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class MineCallInviteUsersController: GroupParticipantsRemoveController { 12 | 13 | private var confirmClosure: (([String]) -> Void)? 14 | 15 | private var existProfiles = [ChatUserProfileProtocol]() 16 | 17 | private var pageSize = UInt(200) 18 | 19 | private var cursor = "" 20 | 21 | public required init(groupId: String, profiles: [ChatUserProfileProtocol],usersClosure: @escaping ([String]) -> Void) { 22 | super.init(group: ChatGroup(id: groupId), profiles: profiles, removeClosure: usersClosure) 23 | self.existProfiles = profiles 24 | self.confirmClosure = usersClosure 25 | } 26 | 27 | required init(group: ChatGroup, profiles: [ChatUserProfileProtocol], removeClosure: @escaping ([String]) -> Void) { 28 | super.init(group: group, profiles: profiles, removeClosure: removeClosure) 29 | } 30 | 31 | required init?(coder: NSCoder) { 32 | fatalError("init(coder:) has not been implemented") 33 | } 34 | 35 | override func createNavigation() -> ChatNavigationBar { 36 | ChatNavigationBar(show: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 44),textAlignment: .left,rightTitle: "Confirm".chat.localize) 37 | } 38 | 39 | override func viewDidLoad() { 40 | super.viewDidLoad() 41 | self.view.cornerRadius(.medium, [.topLeft,.topRight], .clear, 0) 42 | // Do any additional setup after loading the view. 43 | self.navigation.title = "Video Call".localized() 44 | self.navigation.rightItem.title("Confirm".chat.localize, .normal) 45 | self.fetchParticipants() 46 | self.switchTheme(style: Theme.style) 47 | } 48 | 49 | private func fetchParticipants() { 50 | self.service.fetchParticipants(groupId: self.chatGroup.groupId, cursor: self.cursor, pageSize: self.pageSize) { [weak self] result, error in 51 | guard let `self` = self else {return} 52 | if error == nil { 53 | if let list = result?.list { 54 | if self.cursor.isEmpty { 55 | self.participants.removeAll() 56 | self.participants = list.map({ 57 | let profile = ChatUserProfile() 58 | let id = $0 as String 59 | profile.id = id 60 | if let user = ChatUIKitContext.shared?.userCache?[id] { 61 | profile.nickname = user.nickname 62 | profile.avatarURL = user.avatarURL 63 | } 64 | if let user = ChatUIKitContext.shared?.chatCache?[id] { 65 | profile.nickname = user.nickname 66 | profile.avatarURL = user.avatarURL 67 | } 68 | 69 | return profile 70 | }) 71 | if list.count <= self.pageSize { 72 | let profile = ChatUserProfile() 73 | profile.id = self.chatGroup.owner 74 | if let user = ChatUIKitContext.shared?.userCache?[self.chatGroup.owner] { 75 | profile.nickname = user.nickname 76 | profile.avatarURL = user.avatarURL 77 | } 78 | if let user = ChatUIKitContext.shared?.chatCache?[self.chatGroup.owner] { 79 | profile.nickname = user.nickname 80 | profile.avatarURL = user.avatarURL 81 | } 82 | self.participants.insert(profile, at: 0) 83 | } 84 | } else { 85 | self.participants.append(contentsOf: list.map({ 86 | let profile = ChatUserProfile() 87 | profile.id = $0 as String 88 | if let user = ChatUIKitContext.shared?.userCache?[profile.id] { 89 | profile.nickname = user.nickname 90 | profile.avatarURL = user.avatarURL 91 | } 92 | if let user = ChatUIKitContext.shared?.chatCache?[profile.id] { 93 | profile.nickname = user.nickname 94 | profile.avatarURL = user.avatarURL 95 | } 96 | return profile 97 | })) 98 | 99 | } 100 | } 101 | self.cursor = result?.cursor ?? "" 102 | self.participants.removeAll { $0.id == ChatUIKitContext.shared?.currentUserId ?? "" } 103 | self.participantsList.reloadData() 104 | if !self.cursor.isEmpty { 105 | self.fetchParticipants() 106 | } 107 | } else { 108 | self.showToast(toast: error?.errorDescription ?? "Failed to fetch participants") 109 | } 110 | } 111 | } 112 | 113 | override func didSelectRowAt(indexPath: IndexPath) { 114 | if let profile = self.participants[safe: indexPath.row] { 115 | profile.selected = !profile.selected 116 | self.participantsList.reloadData() 117 | } 118 | let count = self.participants.filter({ $0.selected }).count 119 | if count > 0 { 120 | self.navigation.rightItem.isEnabled = true 121 | self.navigation.rightItem.title("Confirm".chat.localize+"(\(count))", .normal) 122 | } else { 123 | self.navigation.rightItem.title("Confirm".chat.localize, .normal) 124 | self.navigation.rightItem.isEnabled = false 125 | } 126 | } 127 | 128 | override func rightAction() { 129 | let userIds = self.participants.filter { $0.selected == true }.map { $0.id } 130 | self.confirmClosure?(userIds) 131 | self.pop() 132 | } 133 | /* 134 | // MARK: - Navigation 135 | 136 | // In a storyboard-based application, you will often want to do a little preparation before navigation 137 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 138 | // Get the new view controller using segue.destination. 139 | // Pass the selected object to the new view controller. 140 | } 141 | */ 142 | 143 | override func switchTheme(style: ThemeStyle) { 144 | super.switchTheme(style: style) 145 | self.navigation.rightItem.setTitleColor(style == .dark ? UIColor.theme.primaryDarkColor:UIColor.theme.primaryLightColor, for: .normal) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/IntegratedFromEaseChatUIKit/Controllers/MineContactRemarkEditViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MineContactRemarkEditViewController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/20. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class MineContactRemarkEditViewController: UIViewController { 12 | 13 | 14 | public private(set) var userId: String = "" 15 | 16 | public private(set) var raw: String = "" 17 | 18 | private var modifySuccess: ((String) -> ())? 19 | 20 | public private(set) lazy var navigation: ChatNavigationBar = { 21 | ChatNavigationBar(textAlignment: .left,rightTitle: "Save".chat.localize) 22 | }() 23 | 24 | lazy var container: UIView = { 25 | UIView(frame: CGRect(x: 16, y: self.navigation.frame.maxY+16, width: self.view.frame.width-32, height: 114)).backgroundColor(Theme.style == .dark ? UIColor.theme.neutralColor3:UIColor.theme.neutralColor95).cornerRadius(.extraSmall) 26 | }() 27 | 28 | public private(set) lazy var contentEditor: CustomTextView = { 29 | CustomTextView(frame: CGRect(x: 16, y: self.container.frame.minY, width: self.view.frame.width-32, height: 72)).delegate(self).font(UIFont.theme.bodyLarge).backgroundColor(.clear) 30 | }() 31 | 32 | lazy var limitCount: UILabel = { 33 | UILabel(frame: CGRect(x: self.container.frame.maxX-70, y: self.container.frame.maxY-35, width: 54, height: 22)).font(UIFont.theme.bodyLarge).textColor(Theme.style == .dark ? UIColor.theme.neutralColor5:UIColor.theme.neutralColor7).textAlignment(.right) 34 | }() 35 | 36 | @objc public required convenience init(userId: String,rawText: String,modifyClosure: @escaping (String) -> Void) { 37 | self.init() 38 | self.userId = userId 39 | self.raw = rawText 40 | self.modifySuccess = modifyClosure 41 | } 42 | 43 | public override func viewDidLoad() { 44 | super.viewDidLoad() 45 | // Do any additional setup after loading the view. 46 | self.contentEditor.contentInset = UIEdgeInsets(top: 6, left: 10, bottom: 0, right: 10) 47 | self.contentEditor.linkTextAttributes = [.foregroundColor:Theme.style == .dark ? UIColor.theme.neutralColor5:UIColor.theme.neutralColor6] 48 | // self.contentEditor.placeHolderColor = Theme.style == .dark ? UIColor.theme.neutralColor5:UIColor.theme.neutralColor6 49 | self.contentEditor.placeholder = "Please input".chat.localize 50 | self.navigation.clickClosure = { [weak self] in 51 | self?.navigationClick(type: $0, indexPath: $1) 52 | } 53 | self.view.addSubViews([self.navigation,self.container,self.contentEditor,self.limitCount]) 54 | self.contentEditor.text = self.raw 55 | self.limitCount.text = "\(self.raw.count)/\(self.textLimit())" 56 | Theme.registerSwitchThemeViews(view: self) 57 | self.switchTheme(style: Theme.style) 58 | 59 | } 60 | 61 | override func viewDidAppear(_ animated: Bool) { 62 | super.viewDidAppear(animated) 63 | self.contentEditor.becomeFirstResponder() 64 | } 65 | 66 | private func navigationClick(type: ChatNavigationBarClickEvent,indexPath: IndexPath?) { 67 | switch type { 68 | case .back: self.pop() 69 | case .rightTitle: self.save() 70 | default: 71 | break 72 | } 73 | } 74 | 75 | private func textLimit() -> Int { 76 | 64 77 | } 78 | 79 | private func save() { 80 | self.view.endEditing(true) 81 | guard let text = self.contentEditor.text else { return } 82 | if text.count > self.textLimit() { 83 | self.showToast(toast: "Reach content character limit.".chat.localize) 84 | } else { 85 | self.modifySuccess?(text) 86 | self.pop() 87 | } 88 | 89 | } 90 | 91 | private func pop() { 92 | if self.navigationController != nil { 93 | self.navigationController?.popViewController(animated: true) 94 | } else { 95 | self.dismiss(animated: true) 96 | } 97 | } 98 | 99 | } 100 | 101 | extension MineContactRemarkEditViewController: UITextViewDelegate { 102 | public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { 103 | if text == "\n" { 104 | return false 105 | } 106 | self.navigation.rightItem.isEnabled = (!(textView.text ?? "").isEmpty || !text.isEmpty) 107 | if (textView.text ?? "").count > self.textLimit(),!text.isEmpty { 108 | self.showToast(toast: "Reach content character limit.".chat.localize) 109 | return false 110 | } else { 111 | return true 112 | } 113 | } 114 | 115 | public func textViewDidChange(_ textView: UITextView) { 116 | let limitCount = self.textLimit() 117 | let count = (textView.text ?? "").count 118 | if count > limitCount { 119 | self.showToast(toast: "Reach content character limit.".chat.localize) 120 | textView.text = textView.text.chat.subStringTo(limitCount) 121 | } 122 | self.limitCount.text = "\(count)/\(limitCount)" 123 | } 124 | } 125 | 126 | extension MineContactRemarkEditViewController: ThemeSwitchProtocol { 127 | public func switchTheme(style: ThemeStyle) { 128 | self.view.backgroundColor = style == .dark ? UIColor.theme.neutralColor1:UIColor.theme.neutralColor98 129 | self.contentEditor.textColor(style == .dark ? UIColor.theme.neutralColor98:UIColor.theme.neutralColor1) 130 | self.contentEditor.tintColor = style == .dark ? UIColor.theme.primaryDarkColor:UIColor.theme.primaryLightColor 131 | } 132 | 133 | 134 | } 135 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/IntegratedFromEaseChatUIKit/Controllers/MineGroupDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MineGroupDetailViewController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/19. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | import EaseCallKit 11 | 12 | final class MineGroupDetailViewController: GroupInfoViewController { 13 | 14 | override func cleanHistoryMessages() { 15 | DialogManager.shared.showAlert(title: "", content: "group_details_button_clearchathistory".chat.localize, showCancel: true, showConfirm: true) { [weak self] _ in 16 | guard let `self` = self else { return } 17 | self.showToast(toast: "Clean successful!".localized()) 18 | ChatClient.shared().chatManager?.getConversationWithConvId(self.chatGroup.groupId)?.deleteAllMessages(nil) 19 | NotificationCenter.default.post(name: Notification.Name("EaseChatUIKit_clean_history_messages"), object: self.chatGroup.groupId) 20 | } 21 | } 22 | 23 | override func viewDidLoad() { 24 | Appearance.contact.detailExtensionActionItems = [ContactListHeaderItem(featureIdentify: "Chat", featureName: "Chat".chat.localize, featureIcon: UIImage(named: "chatTo", in: .chatBundle, with: nil)),ContactListHeaderItem(featureIdentify: "AudioCall", featureName: "AudioCall".chat.localize, featureIcon: UIImage(named: "voice_call", in: .chatBundle, with: nil)),ContactListHeaderItem(featureIdentify: "VideoCall", featureName: "VideoCall".chat.localize, featureIcon: UIImage(named: "video_call", in: .chatBundle, with: nil)),ContactListHeaderItem(featureIdentify: "SearchMessages", featureName: "SearchMessages".chat.localize, featureIcon: UIImage(named: "search_history_messages", in: .chatBundle, with: nil))] 25 | let item = ActionSheetItem(title: "barrage_long_press_menu_report".chat.localize, type: .normal, tag: "report") 26 | self.ownerOptions.insert(item, at: 0) 27 | self.memberOptions.insert(item, at: 0) 28 | super.viewDidLoad() 29 | // Do any additional setup after loading the view. 30 | self.header.status.isHidden = true 31 | } 32 | 33 | 34 | override func headerActions() { 35 | if let chat = Appearance.contact.detailExtensionActionItems.first(where: { $0.featureIdentify == "Chat" }) { 36 | chat.actionClosure = { [weak self] in 37 | self?.processHeaderActionEvents(item: $0) 38 | } 39 | } 40 | if let search = Appearance.contact.detailExtensionActionItems.first(where: { $0.featureIdentify == "SearchMessages" }) { 41 | search.actionClosure = { [weak self] in 42 | self?.processHeaderActionEvents(item: $0) 43 | } 44 | } 45 | if let audioCall = Appearance.contact.detailExtensionActionItems.first(where: { $0.featureIdentify == "AudioCall" }) { 46 | audioCall.actionClosure = { [weak self] in 47 | self?.processHeaderActionEvents(item: $0) 48 | } 49 | } 50 | if let videoCall = Appearance.contact.detailExtensionActionItems.first(where: { $0.featureIdentify == "VideoCall" }) { 51 | videoCall.actionClosure = { [weak self] in 52 | self?.processHeaderActionEvents(item: $0) 53 | } 54 | } 55 | } 56 | 57 | override func processHeaderActionEvents(item: any ContactListHeaderItemProtocol) { 58 | switch item.featureIdentify { 59 | case "Chat": self.alreadyChat() 60 | case "AudioCall": self.groupCall() 61 | case "VideoCall": self.groupCall() 62 | case "SearchMessages": self.searchHistoryMessages() 63 | default: break 64 | } 65 | } 66 | 67 | private func groupCall() { 68 | guard let groupId = self.chatGroup.groupId,let userInfo = ChatUIKitContext.shared?.currentUser else { 69 | self.showToast(toast: "Chat group id is nil") 70 | return 71 | } 72 | let vc = MineCallInviteUsersController(groupId: groupId,profiles: [userInfo]) { [weak self] users in 73 | self?.startGroupCall(users: users) 74 | } 75 | self.present(vc, animated: true) 76 | } 77 | 78 | private func startGroupCall(users: [String]) { 79 | if let groupId = self.chatGroup.groupId { 80 | EaseCallManager.shared().startInviteUsers(users, ext: ["groupId":groupId]) { [weak self] callId, callError in 81 | if callError != nil { 82 | self?.showToast(toast: "\(callError?.errDescription ?? "")") 83 | } 84 | } 85 | } 86 | } 87 | 88 | override func fetchGroupInfo(groupId: String) { 89 | // Fetch group information from the service 90 | self.service.fetchGroupInfo(groupId: groupId) { [weak self] group, error in 91 | guard let `self` = self else { return } 92 | if error == nil, let group = group { 93 | self.chatGroup = group 94 | let showName = self.chatGroup.groupName.isEmpty ? groupId:self.chatGroup.groupName 95 | self.header.nickName.text = showName 96 | self.header.userState = .offline 97 | self.header.detailText = groupId 98 | self.menuList.reloadData() 99 | let profile = ChatUserProfile() 100 | profile.id = self.chatGroup.groupId 101 | profile.nickname = self.chatGroup.groupName 102 | if !self.chatGroup.groupName.isEmpty { 103 | profile.avatarURL = self.chatGroup.settings.ext 104 | self.header.avatarURL = self.chatGroup.settings.ext 105 | } 106 | ChatUIKitContext.shared?.updateCache(type: .group, profile: profile) 107 | } else { 108 | self.chatGroup = ChatGroup(id: groupId) 109 | self.showToast(toast: "\(error?.errorDescription ?? "")") 110 | } 111 | 112 | } 113 | } 114 | 115 | override func disbandRequest() { 116 | self.service.disband(groupId: self.chatGroup.groupId) { [weak self] error in 117 | guard let `self` = self else { return } 118 | if error == nil { 119 | self.showToast(toast: "Group disbanded".localized()) 120 | NotificationCenter.default.post(name: Notification.Name("EaseChatUIKit_leaveGroup"), object: self.chatGroup.groupId) 121 | DispatchQueue.main.asyncAfter(wallDeadline: .now()+1) { 122 | self.pop() 123 | } 124 | } else { 125 | consoleLogInfo("disband error:\(error?.errorDescription ?? "")", type: .error) 126 | } 127 | } 128 | } 129 | 130 | override func rightActions(indexPath: IndexPath) { 131 | switch indexPath.row { 132 | case 0: 133 | DialogManager.shared.showActions(actions: self.chatGroup.permissionType == .owner ? self.ownerOptions:self.memberOptions) { [weak self] item in 134 | guard let `self` = self else { return } 135 | switch item.tag { 136 | case "disband_group": self.disband() 137 | case "transfer_owner": self.transfer() 138 | case "quit_group": self.leave() 139 | case "report": self.reportUser() 140 | default: 141 | break 142 | } 143 | } 144 | default: 145 | break 146 | } 147 | } 148 | 149 | private func reportUser() { 150 | let subject = "Easemob Official DEMO Report \(self.chatGroup.groupId ?? "")".chat.urlEncoded 151 | let body = "Thank you for your feedback. Please describe the content you would like to report and provide the relevant screenshots..".chat.urlEncoded 152 | if let url = URL(string: "mailto:issues@easemob.com?subject=\(subject)&body=\(body)") { 153 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/IntegratedFromEaseChatUIKit/Views/FraudAlertView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FraudAlertView.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2/12/25. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class FraudAlertView: UIView { 12 | 13 | lazy var container: UIView = { 14 | UIView(frame: .zero).backgroundColor(Theme.style == .dark ? UIColor.theme.neutralSpecialColor2:UIColor.theme.neutralSpecialColor9).cornerRadius(4) 15 | }() 16 | 17 | lazy var fraudIcon: UIImageView = { 18 | UIImageView(frame: .zero).image(UIImage(named: "fraud_icon")).contentMode(.scaleAspectFit) 19 | }() 20 | 21 | lazy var fraudContent: LinkRecognizeTextView = { 22 | LinkRecognizeTextView(frame: .zero).attributedText(NSAttributedString { 23 | AttributedText("fraud_alert".localized()).font(.theme.bodySmall).foregroundColor(Theme.style == .dark ? .theme.neutralColor9:.theme.neutralColor3).lineHeight(minimum: 1).lineBreakMode(.byCharWrapping) 24 | Link("Click report".localized(), url: URL(string: "https://www.easemob.com")!).lineBreakMode(.byCharWrapping) 25 | }).backgroundColor(.clear) 26 | }() 27 | 28 | lazy var close: UIButton = { 29 | UIButton(type: .custom).image(UIImage(named: "close"), .normal).addTargetFor(self, action: #selector(closeAction), for: .touchUpInside) 30 | }() 31 | 32 | var closeClosure: (() -> Void)? 33 | 34 | override init(frame: CGRect) { 35 | super.init(frame: frame) 36 | self.addSubview(self.container) 37 | self.container.translatesAutoresizingMaskIntoConstraints = false 38 | self.container.topAnchor.constraint(equalTo: self.topAnchor,constant: 8).isActive = true 39 | self.container.leadingAnchor.constraint(equalTo: self.leadingAnchor,constant: 10).isActive = true 40 | self.container.trailingAnchor.constraint(equalTo: self.trailingAnchor,constant: -10).isActive = true 41 | self.container.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true 42 | self.container.addSubViews([self.fraudIcon,self.fraudContent,self.close]) 43 | self.fraudContent.isEditable = false 44 | self.fraudContent.isScrollEnabled = false 45 | self.fraudContent.contentInset = .zero 46 | self.fraudContent.textContainerInset = .zero 47 | self.fraudContent.textContainer.lineFragmentPadding = 0 48 | 49 | self.fraudContent.layoutManager.allowsNonContiguousLayout = false 50 | self.fraudContent.adjustsFontForContentSizeCategory = true 51 | self.backgroundColor = Theme.style == .dark ? UIColor.theme.neutralColor1:UIColor.theme.neutralColor98 52 | self.fraudContent.linkTextAttributes = [.foregroundColor: Theme.style == .dark ? UIColor.theme.primaryColor6:UIColor.theme.primaryColor5] 53 | self.fraudIcon.translatesAutoresizingMaskIntoConstraints = false 54 | self.close.translatesAutoresizingMaskIntoConstraints = false 55 | self.fraudIcon.topAnchor.constraint(equalTo: self.container.topAnchor,constant: 12).isActive = true 56 | self.fraudIcon.leadingAnchor.constraint(equalTo: self.container.leadingAnchor,constant: 12).isActive = true 57 | self.fraudIcon.widthAnchor.constraint(equalToConstant: 13).isActive = true 58 | self.fraudIcon.heightAnchor.constraint(equalToConstant: 13).isActive = true 59 | self.fraudContent.translatesAutoresizingMaskIntoConstraints = false 60 | self.fraudContent.topAnchor.constraint(equalTo: self.container.topAnchor,constant: 11).isActive = true 61 | self.fraudContent.leadingAnchor.constraint(equalTo: self.fraudIcon.trailingAnchor,constant: 8).isActive = true 62 | self.fraudContent.trailingAnchor.constraint(equalTo: self.container.trailingAnchor,constant: -32).isActive = true 63 | self.fraudContent.bottomAnchor.constraint(equalTo: self.container.bottomAnchor,constant: -7).isActive = true 64 | 65 | self.close.topAnchor.constraint(equalTo: self.container.topAnchor,constant: 12).isActive = true 66 | self.close.trailingAnchor.constraint(equalTo: self.container.trailingAnchor,constant: -14).isActive = true 67 | self.close.widthAnchor.constraint(equalToConstant: 12).isActive = true 68 | self.close.heightAnchor.constraint(equalToConstant: 12).isActive = true 69 | let limitHeight = self.fraudContent.sizeThatFits(CGSize(width: ScreenWidth-24-78, height: CGFloat.greatestFiniteMagnitude)).height 70 | self.heightAnchor.constraint(equalToConstant: limitHeight+8).isActive = true 71 | 72 | } 73 | 74 | required init?(coder: NSCoder) { 75 | fatalError("init(coder:) has not been implemented") 76 | } 77 | 78 | @objc private func closeAction() { 79 | self.removeFromSuperview() 80 | self.closeClosure?() 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Cells/AboutEasemobCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutEasemobCell.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/6. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class AboutEasemobCell: UITableViewCell { 12 | 13 | public private(set) lazy var separatorLine: UIView = { 14 | UIView(frame: CGRect(x: self.textLabel?.frame.minX ?? 16, y: self.contentView.frame.height - 0.5, width: self.contentView.frame.width, height: 0.5)) 15 | }() 16 | 17 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 18 | super.init(style: style, reuseIdentifier: reuseIdentifier) 19 | self.contentView.addSubview(self.separatorLine) 20 | self.contentView.backgroundColor = .clear 21 | self.textLabel?.font = UIFont.theme.labelLarge 22 | self.backgroundColor = .clear 23 | self.accessoryType = .disclosureIndicator 24 | Theme.registerSwitchThemeViews(view: self) 25 | self.switchTheme(style: Theme.style) 26 | } 27 | 28 | override func layoutSubviews() { 29 | super.layoutSubviews() 30 | let axisX = self.textLabel?.frame.minX ?? 16 31 | self.separatorLine.frame = CGRect(x: axisX, y: self.contentView.frame.height - 0.5, width: self.frame.width-axisX, height: 0.5) 32 | } 33 | 34 | required init?(coder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | 38 | 39 | } 40 | 41 | extension AboutEasemobCell: ThemeSwitchProtocol { 42 | 43 | func switchTheme(style: ThemeStyle) { 44 | self.textLabel?.textColor(style == .dark ? UIColor.theme.neutralColor98:UIColor.theme.neutralColor1) 45 | self.accessoryView?.tintColor = style == .dark ? UIColor.theme.neutralColor5:UIColor.theme.neutralColor3 46 | self.accessoryView?.subviews.first?.tintColor = style == .dark ? UIColor.theme.neutralColor5:UIColor.theme.neutralColor3 47 | self.detailTextLabel?.textColor = style == .dark ? UIColor.theme.primaryDarkColor:UIColor.theme.primaryLightColor 48 | self.separatorLine.backgroundColor = style == .dark ? UIColor.theme.neutralColor2:UIColor.theme.neutralColor9 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Cells/ColorHueSettingCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorHueSettingCell.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/8. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class ColorHueSettingCell: UITableViewCell { 12 | 13 | var indexPath = IndexPath(row: 0, section: 0) 14 | 15 | var sliderValueUpdated: ((CGFloat,IndexPath) -> Void)? 16 | 17 | private lazy var colorContainer: UIView = { 18 | UIView(frame: CGRect(x: 16, y: 13, width: 28, height: 28)).cornerRadius(2).layerProperties(UIColor.theme.neutralColor9, 0.5) 19 | }() 20 | 21 | lazy var colorView: UIView = { 22 | UIView(frame: CGRect(x: 1, y: 1, width: 26, height: 26)).backgroundColor(UIColor(hue: Appearance.primaryHue, saturation: 1, lightness: CGFloat(50)/100.0, alpha: 1)) 23 | }() 24 | 25 | lazy var colorSlider: UISlider = { 26 | let slider = UISlider(frame: CGRect(x: self.colorContainer.frame.maxX+16, y: self.colorContainer.frame.minY, width: self.contentView.frame.width-self.colorContainer.frame.maxX-16-47-20, height: 24)) 27 | slider.minimumValue = 0 28 | slider.maximumValue = 1 29 | slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged) 30 | return slider 31 | }() 32 | 33 | lazy var colorValue: UILabel = { 34 | UILabel(frame: CGRect(x: self.contentView.frame.width-47, y: 0, width: 27, height: 18)).font(UIFont.theme.labelMedium).textColor(UIColor.theme.neutralColor5).textAlignment(.right) 35 | }() 36 | 37 | lazy var separatorLine: UIView = { 38 | UIView(frame: .zero) 39 | }() 40 | 41 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 42 | super.init(style: style, reuseIdentifier: reuseIdentifier) 43 | self.backgroundColor = .clear 44 | self.contentView.backgroundColor = .clear 45 | self.contentView.addSubViews([self.colorContainer,self.colorSlider,self.colorValue,self.separatorLine]) 46 | self.colorContainer.addSubview(self.colorView) 47 | Theme.registerSwitchThemeViews(view: self) 48 | self.switchTheme(style: Theme.style) 49 | } 50 | 51 | required init?(coder: NSCoder) { 52 | fatalError("init(coder:) has not been implemented") 53 | } 54 | 55 | override func layoutSubviews() { 56 | super.layoutSubviews() 57 | self.colorContainer.frame = CGRect(x: 16, y: 13, width: 28, height: 28) 58 | self.colorView.frame = CGRect(x: 1, y: 1, width: 26, height: 26) 59 | self.colorSlider.frame = CGRect(x: self.colorContainer.frame.maxX+16, y: self.colorContainer.frame.minY, width: self.contentView.frame.width-self.colorContainer.frame.maxX-16-47-20, height: 24) 60 | self.colorValue.frame = CGRect(x: self.contentView.frame.width-47, y: self.colorSlider.center.y-9, width: 27, height: 18) 61 | self.separatorLine.frame = CGRect(x: self.colorSlider.frame.minX, y: self.contentView.frame.height-0.5, width: self.frame.width-16, height: 0.5) 62 | } 63 | 64 | @objc private func sliderValueChanged() { 65 | self.sliderValueUpdated?(CGFloat(self.colorSlider.value),self.indexPath) 66 | // self.colorView.backgroundColor = UIColor(hue: CGFloat(UInt(self.colorSlider.value))/360, saturation: 1, lightness: CGFloat(UInt(50))/100.0, alpha: 1) 67 | // self.colorValue.text = "\(Int(self.colorSlider.value))" 68 | } 69 | 70 | } 71 | 72 | extension ColorHueSettingCell: ThemeSwitchProtocol { 73 | func switchTheme(style: ThemeStyle) { 74 | self.colorContainer.layer.borderColor = style == .dark ? UIColor.theme.neutralColor2.cgColor:UIColor.theme.neutralColor9.cgColor 75 | self.colorSlider.minimumTrackTintColor = style == .dark ? UIColor.theme.primaryDarkColor:UIColor.theme.primaryLightColor 76 | self.colorValue.textColor = style == .dark ? UIColor.theme.neutralColor5:UIColor.theme.neutralColor6 77 | self.separatorLine.backgroundColor = style == .dark ? UIColor.theme.neutralColor2:UIColor.theme.neutralColor9 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Cells/FeatureSwitchCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FeatureSwitchCell.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/13. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class FeatureSwitchCell: UITableViewCell { 12 | 13 | lazy var featureName: UILabel = { 14 | UILabel(frame: CGRect(x: 16, y: 16, width: self.contentView.frame.width-73, height: 22)).font(UIFont.theme.labelLarge) 15 | }() 16 | 17 | lazy var featureSwitch: UISwitch = { 18 | UISwitch(frame: CGRect(x: self.contentView.frame.width-63, y: 11.5, width: 51, height: 31)) 19 | }() 20 | 21 | lazy var detailContainer: UIView = { 22 | UIView(frame: CGRect(x: 0, y: self.featureName.frame.maxY+20, width: self.frame.width, height: 26)) 23 | }() 24 | 25 | lazy var featureDetail: UILabel = { 26 | UILabel(frame: CGRect(x: 16, y: self.featureName.frame.maxY+24, width: self.contentView.frame.width-32, height: 16)).font(UIFont.theme.bodyMedium) 27 | }() 28 | 29 | lazy var separatorLine: UIView = { 30 | UIView(frame: CGRect(x: 16, y: self.contentView.frame.height-0.5, width: self.contentView.frame.width-16, height: 0.5)) 31 | }() 32 | 33 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 34 | super.init(style: style, reuseIdentifier: reuseIdentifier) 35 | self.backgroundColor = .clear 36 | self.contentView.backgroundColor = .clear 37 | self.contentView.addSubViews([self.featureName,self.featureSwitch,self.detailContainer,self.featureDetail,self.separatorLine]) 38 | Theme.registerSwitchThemeViews(view: self) 39 | self.switchTheme(style: Theme.style) 40 | } 41 | 42 | override func layoutSubviews() { 43 | super.layoutSubviews() 44 | self.featureName.frame = CGRect(x: 16, y: 16, width: self.contentView.frame.width-73, height: 22) 45 | self.detailContainer.frame = CGRect(x: 0, y: self.featureName.frame.maxY+20, width: self.frame.width, height: 26) 46 | self.featureSwitch.frame = CGRect(x: self.contentView.frame.width-63, y: 11.5, width: 51, height: 31) 47 | self.featureDetail.frame = CGRect(x: 16, y: self.featureName.frame.maxY+24, width: self.contentView.frame.width-32, height: 16) 48 | self.separatorLine.frame = CGRect(x: 16, y: self.featureName.frame.maxY+20, width: self.frame.width-16, height: 0.5) 49 | } 50 | 51 | required init?(coder: NSCoder) { 52 | fatalError("init(coder:) has not been implemented") 53 | } 54 | } 55 | 56 | extension FeatureSwitchCell: ThemeSwitchProtocol { 57 | func switchTheme(style: ThemeStyle) { 58 | self.featureName.textColor(style == .dark ? UIColor.theme.neutralColor98:UIColor.theme.neutralColor1) 59 | self.detailContainer.backgroundColor = style == .dark ? UIColor.theme.neutralColor0:UIColor.theme.neutralColor95 60 | self.featureDetail.textColor(style == .dark ? UIColor.theme.neutralColor6:UIColor.theme.neutralColor5) 61 | self.separatorLine.backgroundColor = style == .dark ? UIColor.theme.neutralColor2:UIColor.theme.neutralColor9 62 | self.featureSwitch.onTintColor = style == .dark ? UIColor.theme.primaryDarkColor:UIColor.theme.primaryLightColor 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Cells/LanguageCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LanguageCell.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/7. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class LanguageCell: UITableViewCell { 12 | 13 | lazy var content: UILabel = { 14 | UILabel(frame: CGRect(x: 16, y: 16, width: self.contentView.frame.width-28-32-10, height: 22)).font(UIFont.theme.labelLarge).backgroundColor(.clear) 15 | }() 16 | 17 | lazy var checkbox: UIButton = { 18 | UIButton(type: .custom).frame(CGRect(x: self.contentView.frame.width-16-28, y: (self.contentView.frame.height-28)/2.0, width: 28, height: 28)).backgroundColor(.clear) 19 | }() 20 | 21 | private lazy var separatorLine: UIView = { 22 | UIView(frame: CGRect(x: self.content.frame.minX, y: self.contentView.frame.height-0.5, width: self.contentView.frame.width-self.content.frame.minX, height: 0.5)) 23 | }() 24 | 25 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 26 | super.init(style: style, reuseIdentifier: reuseIdentifier) 27 | self.contentView.backgroundColor = .clear 28 | self.backgroundColor = .clear 29 | self.contentView.addSubViews([self.content,self.checkbox,self.separatorLine]) 30 | Theme.registerSwitchThemeViews(view: self) 31 | self.switchTheme(style: Theme.style) 32 | self.checkbox.isUserInteractionEnabled = false 33 | } 34 | 35 | override func layoutSubviews() { 36 | super.layoutSubviews() 37 | self.content.frame = CGRect(x: 16, y: 16, width: self.contentView.frame.width-28-32-10, height: 22) 38 | self.checkbox.frame = CGRect(x: self.contentView.frame.width-16-28, y: (self.contentView.frame.height-28)/2.0, width: 28, height: 28) 39 | self.separatorLine.frame = CGRect(x: self.content.frame.minX, y: self.contentView.frame.height-0.5, width: self.frame.width-self.content.frame.minX, height: 0.5) 40 | } 41 | 42 | 43 | required init?(coder: NSCoder) { 44 | fatalError("init(coder:) has not been implemented") 45 | } 46 | 47 | } 48 | 49 | extension LanguageCell: ThemeSwitchProtocol { 50 | func switchTheme(style: ThemeStyle) { 51 | var normalImage = UIImage(named: "uncheck") 52 | if style == .dark { 53 | normalImage = normalImage?.withTintColor(UIColor.theme.neutralColor4, renderingMode: .alwaysOriginal) 54 | } 55 | self.checkbox.image(normalImage, .normal) 56 | var selectedImage = UIImage(named: "check") 57 | if style == .dark { 58 | selectedImage = selectedImage?.withTintColor(UIColor.theme.primaryDarkColor, renderingMode: .alwaysOriginal) 59 | } 60 | self.checkbox.image(selectedImage, .selected) 61 | self.separatorLine.backgroundColor(style == .dark ? UIColor.theme.neutralColor2:UIColor.theme.neutralColor9) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Cells/MeMenuCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MeMenuCell.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/5. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | 12 | final class MeMenuCell: UITableViewCell { 13 | 14 | lazy var icon: UIImageView = { 15 | UIImageView(frame: CGRect(x: 16, y: 13, width: self.contentView.frame.height-26, height: self.contentView.frame.height-26)).backgroundColor(.clear) 16 | }() 17 | 18 | lazy var content: UILabel = { 19 | UILabel(frame: CGRect(x: self.icon.frame.maxX+8, y: 16, width: (self.frame.width-32)/2.0, height: 22)).font(UIFont.theme.labelLarge).backgroundColor(.clear) 20 | }() 21 | 22 | lazy var detail: UILabel = { 23 | UILabel(frame: CGRect(x: self.frame.width-156, y: 18, width: 120, height: 18)).font(UIFont.theme.labelMedium).textAlignment(.right) 24 | }() 25 | 26 | public private(set) lazy var separatorLine: UIView = { 27 | UIView(frame: CGRect(x: self.content.frame.minX, y: self.contentView.frame.height - 0.5, width: self.frame.width, height: 0.5)) 28 | }() 29 | 30 | lazy var indicator: UIImageView = { 31 | UIImageView(frame: CGRect(x: self.frame.width-37, y: 0, width: 20, height: 20)).contentMode(.scaleAspectFill).backgroundColor(.clear) 32 | }() 33 | 34 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 35 | super.init(style: style, reuseIdentifier: reuseIdentifier) 36 | self.textLabel?.font(UIFont.theme.labelLarge) 37 | self.backgroundColor = .clear 38 | self.contentView.backgroundColor = .clear 39 | self.contentView.addSubViews([self.icon,self.content,self.detail,self.indicator,self.separatorLine]) 40 | Theme.registerSwitchThemeViews(view: self) 41 | self.switchTheme(style: Theme.style) 42 | } 43 | 44 | override func layoutSubviews() { 45 | super.layoutSubviews() 46 | self.icon.frame = CGRect(x: 16, y: (self.frame.height-28)/2.0, width: 28, height: 28) 47 | self.indicator.frame = CGRect(x: self.frame.width-32, y: (self.frame.height-20)/2.0, width: 10, height: 20) 48 | self.separatorLine.frame = CGRect(x: self.content.frame.minX, y: self.contentView.frame.height - 0.5, width: ScreenWidth, height: 0.5) 49 | } 50 | 51 | func refreshViews(hasIcon: Bool) { 52 | self.icon.isHidden = !hasIcon 53 | if hasIcon { 54 | self.icon.frame = CGRect(x: 16, y: (self.frame.height-28)/2.0, width: 28, height: 28) 55 | self.icon.center = CGPoint(x: 30, y: self.contentView.frame.height/2.0) 56 | self.content.frame = CGRect(x: self.icon.frame.maxX+8, y: 16, width: self.frame.width-32-28-8, height: 22) 57 | self.content.textColor(Theme.style == .dark ? UIColor.theme.neutralColor98:UIColor.theme.neutralColor1) 58 | } else { 59 | self.icon.frame = .zero 60 | self.content.frame = CGRect(x: 16, y: 16, width: self.frame.width-32-28-8, height: 22) 61 | self.content.textColor(Theme.style == .dark ? UIColor.theme.primaryDarkColor:UIColor.theme.primaryLightColor) 62 | } 63 | self.separatorLine.frame = CGRect(x: self.content.frame.minX, y: self.contentView.frame.height - 0.5, width: ScreenWidth, height: 0.5) 64 | self.indicator.isHidden = !hasIcon 65 | } 66 | 67 | 68 | required init?(coder: NSCoder) { 69 | fatalError("init(coder:) has not been implemented") 70 | } 71 | 72 | } 73 | 74 | extension MeMenuCell: ThemeSwitchProtocol { 75 | 76 | func switchTheme(style: ThemeStyle) { 77 | 78 | self.detail.textColor(style == .dark ? UIColor.theme.neutralColor6:UIColor.theme.neutralColor5) 79 | let image = UIImage(named: "chevron_right", in: .chatBundle, with: nil)?.withTintColor(style == .dark ? UIColor.theme.neutralColor5:UIColor.theme.neutralColor3) 80 | self.indicator.image = image 81 | 82 | self.separatorLine.backgroundColor = style == .dark ? UIColor.theme.neutralColor2:UIColor.theme.neutralColor9 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Cells/PersonalInfoCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersonalInfoCell.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/6. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class PersonalInfoCell: UITableViewCell { 12 | 13 | private lazy var titleLabel: UILabel = { 14 | UILabel(frame: CGRect(x: 16, y: 16, width: self.frame.width/2.0-26, height: 22)).font(UIFont.theme.labelLarge).backgroundColor(.clear).font(UIFont.theme.labelLarge) 15 | }() 16 | 17 | private lazy var detailLabel: UILabel = { 18 | UILabel(frame: CGRect(x: self.frame.width/2.0, y: 16, width: self.frame.width/2.0-80, height: 22)).tag(12).backgroundColor(.clear).font(UIFont.theme.labelLarge).textAlignment(.right) 19 | }() 20 | 21 | private lazy var detailImage: ImageView = { 22 | ImageView(frame: CGRect(x: self.frame.width-76, y: 7, width: 40, height: 40)).cornerRadius(Appearance.avatarRadius).contentMode(.scaleToFill) 23 | }() 24 | 25 | private lazy var separatorLine: UIView = { 26 | UIView(frame: CGRect(x: self.titleLabel.frame.minX, y: self.contentView.frame.height - 0.5, width: self.frame.width-self.titleLabel.frame.minX, height: 0.5)) 27 | }() 28 | 29 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 30 | super.init(style: style, reuseIdentifier: reuseIdentifier) 31 | self.backgroundColor = .clear 32 | self.contentView.backgroundColor = .clear 33 | self.contentView.addSubViews([self.titleLabel,self.detailLabel,self.detailImage,self.separatorLine]) 34 | Theme.registerSwitchThemeViews(view: self) 35 | self.switchTheme(style: Theme.style) 36 | } 37 | 38 | required init?(coder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | override func layoutSubviews() { 43 | super.layoutSubviews() 44 | self.titleLabel.frame = CGRect(x: 16, y: 16, width: self.frame.width/2.0-26, height: 22) 45 | self.detailLabel.frame = CGRect(x: self.frame.width/2.0, y: 16, width: self.frame.width/2.0-30, height: 22) 46 | self.detailImage.frame = CGRect(x: self.frame.width-76, y: 7, width: 40, height: 40) 47 | self.separatorLine.frame = CGRect(x: self.titleLabel.frame.minX, y: self.contentView.frame.height - 0.5, width: self.frame.width, height: 0.5) 48 | } 49 | 50 | func refresh(title: String, detail: String) { 51 | self.titleLabel.text = title 52 | if title == "Avatar".localized() { 53 | self.detailImage.isHidden = false 54 | self.detailLabel.isHidden = true 55 | self.detailImage.image(with: detail, placeHolder: Appearance.conversation.singlePlaceHolder) 56 | } else { 57 | self.detailImage.isHidden = true 58 | self.detailLabel.isHidden = false 59 | self.detailLabel.text = detail 60 | } 61 | } 62 | } 63 | 64 | extension PersonalInfoCell: ThemeSwitchProtocol { 65 | func switchTheme(style: ThemeStyle) { 66 | self.titleLabel.textColor(style == .dark ? UIColor.theme.neutralColor98:UIColor.theme.neutralColor1) 67 | self.detailLabel.textColor(style == .dark ? UIColor.theme.neutralColor6:UIColor.theme.neutralColor5) 68 | self.separatorLine.backgroundColor = style == .dark ? UIColor.theme.neutralColor2:UIColor.theme.neutralColor9 69 | self.accessoryView?.tintColor = style == .dark ? UIColor.theme.neutralColor5:UIColor.theme.neutralColor3 70 | self.accessoryView?.subviews.first?.tintColor = style == .dark ? UIColor.theme.neutralColor5:UIColor.theme.neutralColor3 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Controllers/AboutEasemobController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutEasemobController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/6. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class AboutEasemobController: UIViewController { 12 | 13 | private let infos = [ 14 | ["title":"Official Website".localized(),"content":"https://www.huanxin.com","destination":"https://www.huanxin.com"], 15 | ["title":"Hotline".localized(),"content":"400-622-1766","destination":"tel://400-622-1766"], 16 | ["title":"Business Cooperation".localized(),"content":"bd@easemob.com","destination":"mailto:bd@easemob.com"], 17 | ["title":"Channel Cooperation".localized(),"content":"qudao@easemob.com","destination":"mailto:qudao@easemob.com"], 18 | ["title":"Suggestions".localized(),"content":"issues@easemob.com","destination":"mailto:issues@easemob.com"], 19 | ["title":"Privacy Policy".localized(),"content":"https://www.easemob.com/protocol","destination":"https://www.easemob.com/protocol"] 20 | ] 21 | 22 | private lazy var navigation: ChatNavigationBar = { 23 | ChatNavigationBar(show: CGRect(x: 0, y: 0, width: self.view.frame.width, height: NavigationHeight), textAlignment: .left, rightTitle: nil) 24 | }() 25 | 26 | private lazy var header: AboutEasemobHeader = { 27 | AboutEasemobHeader(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 221)) 28 | }() 29 | 30 | private lazy var menuList: UITableView = { 31 | UITableView(frame: CGRect(x: 0, y: NavigationHeight, width: self.view.frame.width, height: self.view.frame.height-NavigationHeight), style: .plain).tableFooterView(UIView()).tableHeaderView(self.header).separatorStyle(.none).dataSource(self).delegate(self).rowHeight(54).backgroundColor(.clear) 32 | }() 33 | 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | self.view.addSubViews([self.navigation,self.menuList]) 37 | self.navigation.title = "About".localized() 38 | self.navigation.clickClosure = { [weak self] _,_ in 39 | self?.navigationController?.popViewController(animated: true) 40 | } 41 | // Do any additional setup after loading the view. 42 | Theme.registerSwitchThemeViews(view: self) 43 | self.switchTheme(style: Theme.style) 44 | } 45 | 46 | 47 | 48 | } 49 | 50 | extension AboutEasemobController: UITableViewDelegate,UITableViewDataSource { 51 | 52 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 53 | self.infos.count 54 | } 55 | 56 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 57 | var cell = tableView.dequeueReusableCell(withIdentifier: "AboutEasemobCell") as? AboutEasemobCell 58 | if cell == nil { 59 | cell = AboutEasemobCell(style: .subtitle, reuseIdentifier: "AboutEasemobCell") 60 | } 61 | cell?.selectionStyle = .none 62 | cell?.textLabel?.text = self.infos[safe:indexPath.row]?["title"] 63 | cell?.detailTextLabel?.text = self.infos[safe:indexPath.row]?["content"] 64 | return cell ?? AboutEasemobCell() 65 | } 66 | 67 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 68 | tableView.deselectRow(at: indexPath, animated: true) 69 | let detail = self.infos[safe:indexPath.row]?["destination"] ?? "" 70 | self.openURL(urlString: detail) 71 | } 72 | 73 | private func openURL(urlString: String) { 74 | if let url = URL(string: urlString), UIApplication.shared.canOpenURL(url) { 75 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 76 | } 77 | } 78 | 79 | 80 | } 81 | 82 | extension AboutEasemobController: ThemeSwitchProtocol { 83 | func switchTheme(style: ThemeStyle) { 84 | self.view.backgroundColor = style == .dark ? UIColor.theme.neutralColor1:UIColor.theme.neutralColor98 85 | self.menuList.reloadData() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Controllers/ColorSettingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorSettingViewController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/7. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class ColorSettingViewController: UIViewController { 12 | 13 | private var sectionTitles = ["primary hue".localized(),"secondary hue".localized(),"error hue".localized(),"neutralHue".localized(),"neutral special hue".localized()] 14 | 15 | private var hues: [CGFloat] { 16 | [Appearance.primaryHue,Appearance.secondaryHue,Appearance.errorHue,Appearance.neutralHue,Appearance.neutralSpecialHue] 17 | } 18 | 19 | private var hueColors: [UIColor] { 20 | [UIColor.theme.primaryLightColor,UIColor.theme.secondaryColor4,UIColor.theme.errorColor5,UIColor.theme.neutralColor5,UIColor.theme.neutralSpecialColor5] 21 | } 22 | 23 | private lazy var navigation: ChatNavigationBar = { 24 | ChatNavigationBar(show: CGRect(x: 0, y: 0, width: self.view.frame.width, height: NavigationHeight), textAlignment: .left, rightTitle: "Confirm".chat.localize) 25 | }() 26 | 27 | private lazy var infoList: UITableView = { 28 | UITableView(frame: CGRect(x: 0, y: self.navigation.frame.maxY, width: self.view.frame.width, height: self.view.frame.height-NavigationHeight), style: .grouped).separatorStyle(.none).tableFooterView(UIView()).backgroundColor(.clear).delegate(self).dataSource(self).rowHeight(54).sectionHeaderHeight(20).sectionFooterHeight(0) 29 | }() 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | self.view.addSubViews([self.navigation,self.infoList]) 34 | // Do any additional setup after loading the view. 35 | self.navigation.title = "color_setting".localized() 36 | self.navigation.editMode = false 37 | self.navigation.clickClosure = { [weak self] in 38 | if $0 == .back { 39 | self?.navigationController?.popViewController(animated: true) 40 | } 41 | if $0 == .rightTitle { 42 | UIColor.ColorTheme.switchHues(hues: [Appearance.primaryHue,Appearance.secondaryHue,Appearance.errorHue,Appearance.neutralHue,Appearance.neutralSpecialHue]) 43 | Theme.switchTheme(style: Theme.style) 44 | self?.navigationController?.popViewController(animated: true) 45 | } 46 | consoleLogInfo("\($1?.row ?? 0)", type: .debug) 47 | } 48 | // Do any additional setup after loading the view. 49 | Theme.registerSwitchThemeViews(view: self) 50 | self.switchTheme(style: Theme.style) 51 | } 52 | 53 | 54 | 55 | 56 | } 57 | 58 | extension ColorSettingViewController: UITableViewDelegate,UITableViewDataSource { 59 | 60 | func numberOfSections(in tableView: UITableView) -> Int { 61 | self.sectionTitles.count 62 | } 63 | 64 | func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 65 | UIView { 66 | UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 20)).backgroundColor(Theme.style == .dark ? UIColor.theme.neutralColor0:UIColor.theme.neutralColor95) 67 | UILabel(frame: CGRect(x: 16, y: 0, width: self.view.frame.width-32, height: 20)).font(UIFont.theme.labelMedium).textColor(Theme.style == .dark ? UIColor.theme.neutralColor6:UIColor.theme.neutralColor5).text(self.sectionTitles[section]) 68 | } 69 | } 70 | 71 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 72 | 1 73 | } 74 | 75 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 76 | var cell = tableView.dequeueReusableCell(withIdentifier: "ColorHueSettingCell") as? ColorHueSettingCell 77 | if cell == nil { 78 | cell = ColorHueSettingCell(style: .default, reuseIdentifier: "ColorHueSettingCell") 79 | } 80 | if let color = self.hueColors[safe: indexPath.section],let hueValue = self.hues[safe: indexPath.section] { 81 | cell?.indexPath = indexPath 82 | cell?.colorView.backgroundColor = color 83 | cell?.colorSlider.setValue(Float(hueValue), animated: true) 84 | cell?.colorValue.text = "\(Int(hueValue*360))" 85 | } 86 | cell?.sliderValueUpdated = { [weak self] in 87 | self?.updateHues(hue: $0, indexPath: $1) 88 | } 89 | cell?.accessoryType = .none 90 | cell?.selectionStyle = .none 91 | return cell ?? UITableViewCell() 92 | } 93 | 94 | private func updateHues(hue: CGFloat,indexPath: IndexPath) { 95 | var color = UIColor.white 96 | switch indexPath.section { 97 | case 0: 98 | Appearance.primaryHue = hue 99 | color = UIColor(hue: Appearance.primaryHue, saturation: 1, lightness: 50/100.0, alpha: 1) 100 | case 1: 101 | Appearance.secondaryHue = hue 102 | color = UIColor(hue: Appearance.secondaryHue, saturation: 1, lightness: 40/100.0, alpha: 1) 103 | case 2: 104 | Appearance.errorHue = hue 105 | color = UIColor(hue: Appearance.errorHue, saturation: 1, lightness: 50/100.0, alpha: 1) 106 | case 3: 107 | Appearance.neutralHue = hue 108 | 109 | Appearance.chat.receiveTextColor = Theme.style == .dark ? UIColor.theme.neutralColor98:UIColor.theme.neutralColor1 110 | 111 | Appearance.chat.sendTextColor = Theme.style == .dark ? UIColor.theme.neutralColor1:UIColor.theme.neutralColor98 112 | 113 | Appearance.chat.receiveTranslationColor = Theme.style == .dark ? UIColor.theme.neutralColor7:UIColor.theme.neutralColor5 114 | color = UIColor(hue: Appearance.neutralHue, saturation: 0.08, lightness: 50/100.0, alpha: 1) 115 | case 4: 116 | Appearance.neutralSpecialHue = hue 117 | Appearance.chat.sendTranslationColor = Theme.style == .dark ? UIColor.theme.neutralSpecialColor2:UIColor.theme.neutralSpecialColor95 118 | color = UIColor(hue: Appearance.neutralSpecialHue, saturation: 0.36, lightness: 50/100.0, alpha: 1) 119 | default: 120 | break 121 | } 122 | let cell = self.infoList.cellForRow(at: indexPath) as? ColorHueSettingCell 123 | cell?.colorView.backgroundColor = color 124 | cell?.colorValue.text = "\(Int(hue*360))" 125 | } 126 | } 127 | 128 | extension ColorSettingViewController: ThemeSwitchProtocol { 129 | func switchTheme(style: ThemeStyle) { 130 | self.view.backgroundColor(style == .dark ? UIColor.theme.neutralColor1:UIColor.theme.neutralColor98) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Controllers/FeatureSwitchViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FeatureSwitchViewController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/13. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | 12 | final class FeatureSwitchViewController: UIViewController { 13 | 14 | @UserDefault("EaseMobChatMessageTranslation", defaultValue: true) var enableTranslation: Bool 15 | 16 | @UserDefault("EaseMobChatMessageReaction", defaultValue: true) var messageReaction: Bool 17 | 18 | @UserDefault("EaseMobChatCreateMessageThread", defaultValue: true) var messageThread: Bool 19 | 20 | @UserDefault("EaseChatDemoPreferencesBlock", defaultValue: true) var block: Bool 21 | 22 | @UserDefault("EaseChatDemoPreferencesTyping", defaultValue: true) var typing: Bool 23 | 24 | 25 | private lazy var jsons: [Dictionary] = { 26 | [["title":"message_translate".localized(),"detail":"message_translate_description".localized(),"withSwitch": true,"switchValue":self.enableTranslation],["title":"group_topic".localized(),"detail":"group_topic_description".localized(),"withSwitch": true,"switchValue":self.messageThread],["title":"message_reaction".localized(),"detail":"message_reaction_description".localized(),"withSwitch": true,"switchValue":self.messageReaction],["title":"block_list".localized(),"detail":"Block Alert".localized(),"withSwitch": true,"switchValue":self.block], 27 | ["title":"typing_indicator".localized(),"detail":"Typing Alert".localized(),"withSwitch": true,"switchValue":self.typing] 28 | ] 29 | }() 30 | 31 | private lazy var navigation: ChatNavigationBar = { 32 | ChatNavigationBar(show: CGRect(x: 0, y: 0, width: self.view.frame.width, height: NavigationHeight), textAlignment: .left, rightTitle: nil) 33 | }() 34 | 35 | private lazy var featureList: UITableView = { 36 | UITableView(frame: CGRect(x: 0, y: NavigationHeight, width: self.view.frame.width, height: self.view.frame.height), style: .plain).delegate(self).dataSource(self).tableFooterView(UIView()).rowHeight(80).backgroundColor(.clear).separatorStyle(.none) 37 | }() 38 | 39 | override func viewDidLoad() { 40 | super.viewDidLoad() 41 | 42 | // Do any additional setup after loading the view. 43 | self.view.addSubViews([self.navigation,self.featureList]) 44 | self.navigation.title = "feature_switch".localized() 45 | self.navigation.clickClosure = { [weak self] _,_ in 46 | self?.navigationController?.popViewController(animated: true) 47 | } 48 | // Do any additional setup after loading the view. 49 | Theme.registerSwitchThemeViews(view: self) 50 | self.switchTheme(style: Theme.style) 51 | } 52 | 53 | 54 | } 55 | 56 | extension FeatureSwitchViewController: UITableViewDelegate,UITableViewDataSource { 57 | 58 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 59 | self.jsons.count 60 | } 61 | 62 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 63 | var cell = tableView.dequeueReusableCell(withIdentifier: "FeatureSwitchCell") as? FeatureSwitchCell 64 | if cell == nil { 65 | cell = FeatureSwitchCell(style: .default, reuseIdentifier: "FeatureSwitchCell") 66 | } 67 | let info = self.jsons[indexPath.row] 68 | cell?.featureName.text = info["title"] as? String 69 | cell?.featureDetail.text = info["detail"] as? String 70 | if let withSwitch = info["withSwitch"] as? Bool, withSwitch { 71 | cell?.featureSwitch.tag = indexPath.row 72 | cell?.featureSwitch.addTarget(self, action: #selector(switchAction(_:)), for: .valueChanged) 73 | cell?.featureSwitch.isOn = info["switchValue"] as? Bool ?? false 74 | } 75 | return cell ?? UITableViewCell() 76 | } 77 | 78 | @objc private func switchAction(_ sender: UISwitch) { 79 | var info = self.jsons[sender.tag] 80 | info["switchValue"] = sender.isOn 81 | guard let switchTitle = info["title"] as? String else { return } 82 | switch switchTitle { 83 | case "message_translate".localized(): 84 | Appearance.chat.enableTranslation = sender.isOn 85 | self.enableTranslation = sender.isOn 86 | case "group_topic".localized(): 87 | self.messageThread = sender.isOn 88 | if sender.isOn { 89 | if !Appearance.chat.contentStyle.contains(.withMessageThread) { 90 | Appearance.chat.contentStyle.append(.withMessageThread) 91 | } 92 | } else { 93 | Appearance.chat.contentStyle.removeAll { $0 == .withMessageThread } 94 | } 95 | case "message_reaction".localized(): 96 | self.messageReaction = sender.isOn 97 | if sender.isOn { 98 | if !Appearance.chat.contentStyle.contains(.withMessageReaction) { 99 | Appearance.chat.contentStyle.append(.withMessageReaction) 100 | } 101 | } else { 102 | Appearance.chat.contentStyle.removeAll { $0 == .withMessageReaction } 103 | } 104 | case "block_list".localized(): 105 | self.block = sender.isOn 106 | Appearance.contact.enableBlock = sender.isOn 107 | case "typing_indicator".localized(): 108 | self.typing = sender.isOn 109 | Appearance.chat.enableTyping = sender.isOn 110 | default: 111 | break 112 | } 113 | 114 | 115 | } 116 | 117 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 118 | tableView.deselectRow(at: indexPath, animated: true) 119 | } 120 | } 121 | 122 | extension FeatureSwitchViewController: ThemeSwitchProtocol { 123 | func switchTheme(style: ThemeStyle) { 124 | self.view.backgroundColor = style == .dark ? UIColor.theme.neutralColor1:UIColor.theme.neutralColor98 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Controllers/LanguageSettingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LanguageSettingViewController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/7. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class LanguageSettingViewController: UIViewController { 12 | 13 | @UserDefault("EaseChatDemoPreferencesLanguage", defaultValue: "zh-Hans") var language: String 14 | 15 | private var infos = ["Chinese".localized(),"English".localized()] 16 | 17 | private var languageRawValues = ["zh-Hans","en"] 18 | 19 | private lazy var navigation: ChatNavigationBar = { 20 | ChatNavigationBar(show: CGRect(x: 0, y: 0, width: self.view.frame.width, height: NavigationHeight),textAlignment: .left,rightTitle: "Confirm".chat.localize) 21 | }() 22 | 23 | private lazy var infoList: UITableView = { 24 | UITableView(frame: CGRect(x: 0, y: self.navigation.frame.maxY, width: self.view.frame.width, height: self.view.frame.height-NavigationHeight), style: .plain).separatorStyle(.none).tableFooterView(UIView()).backgroundColor(.clear).delegate(self).dataSource(self).rowHeight(54) 25 | }() 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | 30 | self.view.addSubViews([self.navigation,self.infoList]) 31 | self.navigation.title = "language_setting".localized() 32 | self.navigation.clickClosure = { [weak self] in 33 | consoleLogInfo("\($1?.row ?? 0)", type: .debug) 34 | switch $0 { 35 | case .back: 36 | self?.navigationController?.popViewController(animated: true) 37 | case .rightTitle: 38 | DialogManager.shared.showAlert(title: "Switch Language".localized(), content: "Switch Language".localized()+"Terminal Alert".localized(), showCancel: true, showConfirm: true) { [weak self] _ in 39 | guard let `self` = self else { return } 40 | Appearance.ease_chat_language = LanguageType(rawValue: self.language) ?? .Chinese 41 | exit(0) 42 | } 43 | default: 44 | break 45 | } 46 | 47 | } 48 | // Do any additional setup after loading the view. 49 | Theme.registerSwitchThemeViews(view: self) 50 | self.switchTheme(style: Theme.style) 51 | } 52 | 53 | } 54 | 55 | extension LanguageSettingViewController: UITableViewDelegate,UITableViewDataSource { 56 | 57 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 58 | self.infos.count 59 | } 60 | 61 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 62 | var cell = tableView.dequeueReusableCell(withIdentifier: "LanguageCell") as? LanguageCell 63 | if cell == nil { 64 | cell = LanguageCell(style: .default, reuseIdentifier: "LanguageCell") 65 | } 66 | if let title = self.infos[safe:indexPath.row],let languageCode = self.languageRawValues[safe: indexPath.row] { 67 | cell?.content.text = title 68 | if self.language == languageCode { 69 | cell?.checkbox.isSelected = true 70 | } else { 71 | cell?.checkbox.isSelected = false 72 | } 73 | } 74 | cell?.accessoryType = .none 75 | cell?.selectionStyle = .none 76 | return cell ?? UITableViewCell() 77 | } 78 | 79 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 80 | tableView.deselectRow(at: indexPath, animated: true) 81 | if let code = self.languageRawValues[safe:indexPath.row] { 82 | self.language = code 83 | } 84 | self.infoList.reloadData() 85 | } 86 | } 87 | 88 | extension LanguageSettingViewController: ThemeSwitchProtocol { 89 | func switchTheme(style: ThemeStyle) { 90 | self.view.backgroundColor(style == .dark ? UIColor.theme.neutralColor1:UIColor.theme.neutralColor98) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Controllers/NotificationSettingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationSettingViewController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/12. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class NotificationSettingViewController: UIViewController { 12 | 13 | private lazy var navigation: ChatNavigationBar = { 14 | ChatNavigationBar(show: CGRect(x: 0, y: 0, width: self.view.frame.width, height: NavigationHeight),textAlignment: .left) 15 | }() 16 | 17 | private lazy var container: UIView = { 18 | UIView(frame: .zero) 19 | }() 20 | 21 | private lazy var settingName: UILabel = { 22 | UILabel(frame: CGRect(x: 16, y: self.navigation.frame.maxY+16, width: (self.view.frame.width-32)/2.0, height: 22)).font(UIFont.theme.labelLarge).text("Offline Message Push".localized()) 23 | }() 24 | 25 | private lazy var settingSwitch: UISwitch = { 26 | UISwitch(frame: CGRect(x: self.view.frame.width-63, y: self.navigation.frame.maxY+11.5, width: 51, height: 31)) 27 | }() 28 | 29 | private lazy var separatorLine: UIView = { 30 | UIView(frame: CGRect(x: 16, y: self.settingName.frame.maxY+16, width: self.view.frame.width-16, height: 0.5)) 31 | }() 32 | 33 | private lazy var alert: UILabel = { 34 | UILabel(frame: .zero).font(UIFont.theme.bodyMedium).text("Notification Alert".localized()).numberOfLines(0).backgroundColor(.clear) 35 | }() 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | // Do any additional setup after loading the view. 40 | self.view.addSubViews([self.navigation,self.settingName,self.settingSwitch,self.separatorLine,self.container]) 41 | self.container.addSubview(self.alert) 42 | self.container.translatesAutoresizingMaskIntoConstraints = false 43 | self.alert.translatesAutoresizingMaskIntoConstraints = false 44 | NSLayoutConstraint.activate([ 45 | self.container.topAnchor.constraint(equalTo: self.separatorLine.bottomAnchor), 46 | self.container.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), 47 | self.container.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), 48 | 49 | self.alert.topAnchor.constraint(equalTo: self.container.topAnchor,constant: 2), 50 | self.alert.leadingAnchor.constraint(equalTo: self.container.leadingAnchor,constant: 16), 51 | self.alert.trailingAnchor.constraint(equalTo: self.container.trailingAnchor,constant: -16), 52 | self.alert.bottomAnchor.constraint(equalTo: self.container.bottomAnchor,constant: -2) 53 | ]) 54 | self.navigation.title = "Notification".localized() 55 | self.settingSwitch.addTarget(self, action: #selector(valueChanged(sender:)), for: .touchUpInside) 56 | self.navigation.clickClosure = { [weak self] _,_ in 57 | self?.navigationController?.popViewController(animated: true) 58 | } 59 | Theme.registerSwitchThemeViews(view: self) 60 | self.switchTheme(style: Theme.style) 61 | self.fetchSilentMode() 62 | } 63 | 64 | private func fetchSilentMode() { 65 | ChatClient.shared().pushManager?.getSilentModeForAll(completion: { [weak self] result, error in 66 | guard let `self` = self else { return } 67 | if error == nil { 68 | if let remind = result?.remindType { 69 | self.settingSwitch.isOn = remind == .all ? true:false 70 | } 71 | } else { 72 | consoleLogInfo("fetchSilentMode error:\(error?.errorDescription ?? "")", type: .error) 73 | self.showToast(toast: "fetchSilentMode error:\(error?.errorDescription ?? "")") 74 | } 75 | }) 76 | } 77 | 78 | @objc private func valueChanged(sender: UISwitch) { 79 | let params = SilentModeParam() 80 | params.remindType = sender.isOn ? .all:.mentionOnly 81 | ChatClient.shared().pushManager?.setSilentModeForAll(params, completion: { [weak self] result, error in 82 | if error != nil { 83 | self?.settingSwitch.isOn = true 84 | consoleLogInfo("Set notification error:\(error?.errorDescription ?? "")", type: .error) 85 | self?.showToast(toast: "Set notification error:\(error?.errorDescription ?? "")") 86 | } 87 | }) 88 | } 89 | } 90 | 91 | extension NotificationSettingViewController: ThemeSwitchProtocol { 92 | func switchTheme(style: ThemeStyle) { 93 | self.view.backgroundColor(style == .dark ? UIColor.theme.neutralColor1:UIColor.theme.neutralColor98) 94 | self.container.backgroundColor = style == .dark ? UIColor.theme.neutralColor0:UIColor.theme.neutralColor95 95 | self.settingName.textColor = style == .dark ? UIColor.theme.neutralColor98:UIColor.theme.neutralColor1 96 | self.settingSwitch.onTintColor = style == .dark ? UIColor.theme.primaryDarkColor:UIColor.theme.primaryLightColor 97 | self.separatorLine.backgroundColor(style == .dark ? UIColor.theme.neutralColor2:UIColor.theme.neutralColor9) 98 | self.alert.textColor(style == .dark ? UIColor.theme.neutralColor6:UIColor.theme.neutralColor5) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Controllers/PrivacyPolicyViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrivacyPolicyViewController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/6/3. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class PrivacyPolicyViewController: UIViewController { 12 | 13 | private lazy var navigation: ChatNavigationBar = { 14 | ChatNavigationBar(show: CGRect(x: 0, y: 0, width: self.view.frame.width, height: NavigationHeight), textAlignment: .left, rightTitle: nil) 15 | }() 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | self.view.addSubViews([self.navigation]) 20 | self.navigation.title = "feature_switch".localized() 21 | self.navigation.clickClosure = { [weak self] _,_ in 22 | self?.navigationController?.popViewController(animated: true) 23 | } 24 | // Do any additional setup after loading the view. 25 | } 26 | 27 | 28 | /* 29 | // MARK: - Navigation 30 | 31 | // In a storyboard-based application, you will often want to do a little preparation before navigation 32 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 33 | // Get the new view controller using segue.destination. 34 | // Pass the selected object to the new view controller. 35 | } 36 | */ 37 | 38 | } 39 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Controllers/PrivacySettingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrivacySettingViewController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/6/4. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class PrivacySettingViewController: UIViewController { 12 | 13 | 14 | private lazy var jsons: [Dictionary] = { 15 | Appearance.contact.enableBlock ? [ 16 | ["title":"block_list".localized(),"detail":"","withSwitch": false,"switchValue":false] 17 | ]:[ 18 | ] 19 | }() 20 | 21 | private lazy var datas: [DetailInfo] = { 22 | self.jsons.map { 23 | let info = DetailInfo() 24 | info.setValuesForKeys($0) 25 | return info 26 | } 27 | }() 28 | 29 | private lazy var navigation: ChatNavigationBar = { 30 | ChatNavigationBar(show: CGRect(x: 0, y: 0, width: self.view.frame.width, height: NavigationHeight), textAlignment: .left, rightTitle: nil) 31 | }() 32 | 33 | private lazy var featureList: UITableView = { 34 | UITableView(frame: CGRect(x: 0, y: NavigationHeight, width: self.view.frame.width, height: self.view.frame.height), style: .plain).delegate(self).dataSource(self).tableFooterView(UIView()).rowHeight(54).backgroundColor(.clear).separatorStyle(.none) 35 | }() 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | // Do any additional setup after loading the view. 41 | self.view.addSubViews([self.navigation,self.featureList]) 42 | self.navigation.title = "Privacy".localized() 43 | self.navigation.clickClosure = { [weak self] _,_ in 44 | self?.navigationController?.popViewController(animated: true) 45 | } 46 | // Do any additional setup after loading the view. 47 | Theme.registerSwitchThemeViews(view: self) 48 | self.switchTheme(style: Theme.style) 49 | } 50 | 51 | 52 | } 53 | 54 | extension PrivacySettingViewController: UITableViewDelegate,UITableViewDataSource { 55 | 56 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 57 | self.datas.count 58 | } 59 | 60 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 61 | var cell = tableView.dequeueReusableCell(withIdentifier: "PrivacySettingCell") as? DetailInfoListCell 62 | if cell == nil { 63 | cell = DetailInfoListCell(style: .value2, reuseIdentifier: "PrivacySettingCell") 64 | } 65 | let info = self.jsons[indexPath.row] 66 | let withSwitch = info["withSwitch"] as? Bool ?? false 67 | let switchValue = info["switchValue"] as? Bool ?? false 68 | if let info = self.datas[safe: indexPath.row] { 69 | cell?.refresh(info: info) 70 | } 71 | cell?.valueChanged = { [weak self] in 72 | self?.switchChanged(isOn: $0, indexPath: $1) 73 | } 74 | // cell?.accessoryType = withSwitch ? .none:.disclosureIndicator 75 | return cell ?? UITableViewCell() 76 | } 77 | 78 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 79 | tableView.deselectRow(at: indexPath, animated: true) 80 | let vc = BlockContactsViewController() 81 | self.navigationController?.pushViewController(vc, animated: true) 82 | } 83 | 84 | func switchChanged(isOn: Bool, indexPath: IndexPath) { 85 | if let title = self.datas[safe: indexPath.row]?.title { 86 | // switch title { 87 | // case "typing_indicator".localized(): 88 | // Appearance.chat.enableTyping = isOn 89 | // self.typing = isOn 90 | // default: 91 | // break 92 | // } 93 | 94 | } 95 | } 96 | 97 | } 98 | 99 | extension PrivacySettingViewController: ThemeSwitchProtocol { 100 | func switchTheme(style: ThemeStyle) { 101 | self.view.backgroundColor = style == .dark ? UIColor.theme.neutralColor1:UIColor.theme.neutralColor98 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Controllers/ShowMenuStyleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShowMenuStyleViewController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/10/10. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | enum ShowMenuStyle { 12 | case longPress 13 | case attachment 14 | } 15 | 16 | final class ShowMenuStyleViewController: UIViewController { 17 | 18 | @UserDefault("EaseChatDemoPreferencesLongPressStyle", defaultValue: 0) var longPressStyle: UInt8 19 | 20 | @UserDefault("EaseChatDemoPreferencesAttachmentStyle", defaultValue: 0) var attachmentStyle: UInt8 21 | 22 | public private(set) var style: ShowMenuStyle = .longPress 23 | 24 | private var menus = ["style1".localized(),"style2".localized()] 25 | 26 | private var styleRawValue: UInt8 = 0 27 | 28 | private lazy var navigation: ChatNavigationBar = { 29 | ChatNavigationBar(show: CGRect(x: 0, y: 0, width: self.view.frame.width, height: NavigationHeight),textAlignment: .left,rightTitle: "Confirm".chat.localize) 30 | }() 31 | 32 | private lazy var menuList: UITableView = { 33 | UITableView(frame: CGRect(x: 0, y: self.navigation.frame.maxY, width: self.view.frame.width, height: CGFloat(54*self.menus.count)), style: .plain).separatorStyle(.none).tableFooterView(UIView()).backgroundColor(.clear).delegate(self).dataSource(self).rowHeight(54) 34 | }() 35 | 36 | private lazy var stylePreviewLabel: UILabel = { 37 | UILabel(frame: .zero).font(UIFont.theme.labelLarge).textColor(UIColor.theme.neutralColor5).backgroundColor(.clear).textAlignment(.center).text("style_preview".localized()) 38 | }() 39 | 40 | private lazy var stylePreviewContainer: UIImageView = { 41 | UIImageView(frame: .zero).contentMode(.scaleAspectFit).backgroundColor(.clear) 42 | }() 43 | 44 | public required init(style: ShowMenuStyle) { 45 | super.init(nibName: nil, bundle: nil) 46 | self.style = style 47 | } 48 | 49 | required init?(coder: NSCoder) { 50 | fatalError("init(coder:) has not been implemented") 51 | } 52 | 53 | override func viewDidLoad() { 54 | super.viewDidLoad() 55 | self.styleRawValue = self.style == .longPress ? self.longPressStyle:self.attachmentStyle 56 | self.view.addSubViews([self.navigation,self.menuList,self.stylePreviewLabel,self.stylePreviewContainer]) 57 | self.setPreviewConstrains() 58 | self.navigation.title = self.style == .longPress ? "long_press_style".localized():"attachment_menu_style".localized() 59 | self.navigation.clickClosure = { [weak self] in 60 | consoleLogInfo("\($1?.row ?? 0)", type: .debug) 61 | switch $0 { 62 | case .back: 63 | self?.navigationController?.popViewController(animated: true) 64 | case .rightTitle: 65 | guard let `self` = self else { return } 66 | if self.style == .longPress { 67 | self.longPressStyle = self.styleRawValue 68 | Appearance.chat.messageLongPressMenuStyle = MessageLongPressMenuStyle(rawValue: UInt8(self.styleRawValue)) ?? .actionSheet 69 | } else { 70 | self.attachmentStyle = self.styleRawValue 71 | Appearance.chat.messageAttachmentMenuStyle = MessageAttachmentMenuStyle(rawValue: UInt8(self.styleRawValue)) ?? .actionSheet 72 | } 73 | self.navigationController?.popViewController(animated: true) 74 | default: 75 | break 76 | } 77 | 78 | } 79 | // Do any additional setup after loading the view. 80 | Theme.registerSwitchThemeViews(view: self) 81 | self.switchTheme(style: Theme.style) 82 | } 83 | 84 | private func setPreviewConstrains() { 85 | self.stylePreviewContainer.translatesAutoresizingMaskIntoConstraints = false 86 | self.stylePreviewLabel.translatesAutoresizingMaskIntoConstraints = false 87 | 88 | NSLayoutConstraint.activate([ 89 | 90 | self.stylePreviewContainer.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 16), 91 | self.stylePreviewContainer.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -16), 92 | self.stylePreviewContainer.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -24-BottomBarHeight), 93 | 94 | self.stylePreviewLabel.bottomAnchor.constraint(equalTo: self.stylePreviewContainer.topAnchor, constant: -24), 95 | self.stylePreviewLabel.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 16), 96 | self.stylePreviewLabel.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -16), 97 | self.stylePreviewLabel.heightAnchor.constraint(equalToConstant: 18) 98 | ]) 99 | } 100 | 101 | } 102 | 103 | extension ShowMenuStyleViewController: UITableViewDelegate,UITableViewDataSource { 104 | 105 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 106 | self.menus.count 107 | } 108 | 109 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 110 | var cell = tableView.dequeueReusableCell(withIdentifier: "MenuStyleCell") as? LanguageCell 111 | if cell == nil { 112 | cell = LanguageCell(style: .default, reuseIdentifier: "MenuStyleCell") 113 | } 114 | if let title = self.menus[safe:indexPath.row] { 115 | cell?.content.text = title 116 | if indexPath.row == self.styleRawValue { 117 | cell?.checkbox.isSelected = true 118 | } else { 119 | cell?.checkbox.isSelected = false 120 | } 121 | } 122 | cell?.accessoryType = .none 123 | cell?.selectionStyle = .none 124 | return cell ?? UITableViewCell() 125 | } 126 | 127 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 128 | tableView.deselectRow(at: indexPath, animated: true) 129 | self.styleRawValue = UInt8(indexPath.row) 130 | self.menuList.reloadData() 131 | self.switchTheme(style: Theme.style) 132 | } 133 | } 134 | 135 | extension ShowMenuStyleViewController: ThemeSwitchProtocol { 136 | func switchTheme(style: ThemeStyle) { 137 | self.view.backgroundColor(style == .dark ? UIColor.theme.neutralColor1:UIColor.theme.neutralColor98) 138 | var imageName = "" 139 | if self.style == .longPress { 140 | if self.styleRawValue == 0 { 141 | imageName = "msgmenu_style1_" 142 | } else { 143 | imageName = "msgmenu_style2_" 144 | } 145 | } else { 146 | if self.styleRawValue == 0 { 147 | imageName = "attmsg_style1_" 148 | } else { 149 | imageName = "attmsg_style2_" 150 | } 151 | 152 | } 153 | if style == .dark { 154 | imageName.append("ondark") 155 | } else { 156 | imageName.append("onlight") 157 | } 158 | self.stylePreviewContainer.image = UIImage(named: imageName) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Controllers/ThemesSettingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThemesViewController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/8. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class ThemesSettingViewController: UIViewController { 12 | 13 | @UserDefault("EaseChatDemoPreferencesTheme", defaultValue: 0) var theme: UInt 14 | 15 | private var infos = ["Classic".localized(),"Smart".localized()] 16 | 17 | private var selectIndexPath = IndexPath(row: 0, section: 0) 18 | 19 | private lazy var navigation: ChatNavigationBar = { 20 | ChatNavigationBar(show: CGRect(x: 0, y: 0, width: self.view.frame.width, height: NavigationHeight),textAlignment: .left,rightTitle: "Confirm".chat.localize) 21 | }() 22 | 23 | private lazy var infoList: UITableView = { 24 | UITableView(frame: CGRect(x: 0, y: self.navigation.frame.maxY, width: self.view.frame.width, height: self.view.frame.height-NavigationHeight), style: .plain).separatorStyle(.none).tableFooterView(UIView()).backgroundColor(.clear).delegate(self).dataSource(self).rowHeight(54) 25 | }() 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | self.selectIndexPath = IndexPath(row: Int(self.theme), section: 0) 30 | self.view.addSubViews([self.navigation,self.infoList]) 31 | self.navigation.title = "switch_theme".localized() 32 | self.navigation.clickClosure = { [weak self] in 33 | consoleLogInfo("\($1?.row ?? 0)", type: .debug) 34 | switch $0 { 35 | case .back: 36 | self?.navigationController?.popViewController(animated: true) 37 | case .rightTitle: 38 | DialogManager.shared.showAlert(title: "Switch Theme".localized(), content: "Switch Theme".localized()+"Terminal Alert".localized(), showCancel: true, showConfirm: true) { [weak self] _ in 39 | guard let `self` = self else { return } 40 | self.theme = UInt(self.selectIndexPath.row) 41 | DispatchQueue.main.asyncAfter(wallDeadline: .now()+0.5) { 42 | exit(0) 43 | } 44 | } 45 | default: 46 | break 47 | } 48 | 49 | } 50 | // Do any additional setup after loading the view. 51 | Theme.registerSwitchThemeViews(view: self) 52 | self.switchTheme(style: Theme.style) 53 | } 54 | 55 | 56 | 57 | 58 | } 59 | 60 | extension ThemesSettingViewController: UITableViewDelegate,UITableViewDataSource { 61 | 62 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 63 | self.infos.count 64 | } 65 | 66 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 67 | var cell = tableView.dequeueReusableCell(withIdentifier: "ThemeCell") as? LanguageCell 68 | if cell == nil { 69 | cell = LanguageCell(style: .default, reuseIdentifier: "ThemeCell") 70 | } 71 | if let title = self.infos[safe:indexPath.row] { 72 | cell?.content.text = title 73 | cell?.checkbox.isSelected = self.selectIndexPath.row == indexPath.row 74 | } 75 | cell?.accessoryType = .none 76 | cell?.selectionStyle = .none 77 | return cell ?? UITableViewCell() 78 | } 79 | 80 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 81 | tableView.deselectRow(at: indexPath, animated: true) 82 | self.selectIndexPath = indexPath 83 | self.infoList.reloadData() 84 | } 85 | } 86 | 87 | extension ThemesSettingViewController: ThemeSwitchProtocol { 88 | func switchTheme(style: ThemeStyle) { 89 | self.view.backgroundColor(style == .dark ? UIColor.theme.neutralColor1:UIColor.theme.neutralColor98) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Controllers/TranslateLanguageSettingController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TranslateLanguageSettingController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/6/11. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class TranslateLanguageSettingController: UIViewController { 12 | 13 | @UserDefault("EaseChatDemoTranslateTargetLanguage", defaultValue: "zh-Hans") var language: String 14 | 15 | private var infos = ["Chinese".localized(),"English".localized()] 16 | 17 | private var languageRawValues = ["zh-Hans","en"] 18 | 19 | private lazy var navigation: ChatNavigationBar = { 20 | ChatNavigationBar(show: CGRect(x: 0, y: 0, width: self.view.frame.width, height: NavigationHeight),textAlignment: .left) 21 | }() 22 | 23 | private lazy var infoList: UITableView = { 24 | UITableView(frame: CGRect(x: 0, y: self.navigation.frame.maxY, width: self.view.frame.width, height: self.view.frame.height-NavigationHeight), style: .plain).separatorStyle(.none).tableFooterView(UIView()).backgroundColor(.clear).delegate(self).dataSource(self).rowHeight(54) 25 | }() 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | 30 | self.view.addSubViews([self.navigation,self.infoList]) 31 | self.navigation.title = "translate_language_setting".localized() 32 | self.navigation.clickClosure = { [weak self] in 33 | consoleLogInfo("\($1?.row ?? 0)", type: .debug) 34 | switch $0 { 35 | case .back: 36 | self?.navigationController?.popViewController(animated: true) 37 | default: 38 | break 39 | } 40 | 41 | } 42 | // Do any additional setup after loading the view. 43 | Theme.registerSwitchThemeViews(view: self) 44 | self.switchTheme(style: Theme.style) 45 | } 46 | 47 | } 48 | 49 | extension TranslateLanguageSettingController: UITableViewDelegate,UITableViewDataSource { 50 | 51 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 52 | self.infos.count 53 | } 54 | 55 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 56 | var cell = tableView.dequeueReusableCell(withIdentifier: "LanguageCell") as? LanguageCell 57 | if cell == nil { 58 | cell = LanguageCell(style: .default, reuseIdentifier: "LanguageCell") 59 | } 60 | if let title = self.infos[safe:indexPath.row],let languageCode = self.languageRawValues[safe: indexPath.row] { 61 | cell?.content.text = title 62 | if self.language == languageCode { 63 | cell?.checkbox.isSelected = true 64 | } else { 65 | cell?.checkbox.isSelected = false 66 | } 67 | } 68 | cell?.accessoryType = .none 69 | cell?.selectionStyle = .none 70 | return cell ?? UITableViewCell() 71 | } 72 | 73 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 74 | tableView.deselectRow(at: indexPath, animated: true) 75 | if let code = self.languageRawValues[safe:indexPath.row] { 76 | self.language = code 77 | } 78 | Appearance.chat.targetLanguage = LanguageType(rawValue: self.language) ?? .Chinese 79 | self.infoList.reloadData() 80 | } 81 | } 82 | 83 | extension TranslateLanguageSettingController: ThemeSwitchProtocol { 84 | func switchTheme(style: ThemeStyle) { 85 | self.view.backgroundColor(style == .dark ? UIColor.theme.neutralColor1:UIColor.theme.neutralColor98) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Me/Views/AboutEasemobHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutEasemobHeader.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/6. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class AboutEasemobHeader: UIView { 12 | 13 | private lazy var icon: UIImageView = { 14 | UIImageView(frame: CGRect(x: self.frame.width/2.0-36, y: 20, width: 72, height: 72)).backgroundColor(.clear).contentMode(.scaleAspectFit).image(UIImage(named: "appicon")) 15 | }() 16 | 17 | private lazy var applicationName: UILabel = { 18 | UILabel(frame: CGRect(x: 80, y: self.icon.frame.maxY+8, width: self.frame.width-160, height: 22)).font(UIFont.theme.labelLarge).backgroundColor(.clear).textAlignment(.center) 19 | }() 20 | 21 | private lazy var demo_version: UILabel = { 22 | UILabel(frame: CGRect(x: 80, y: self.applicationName.frame.maxY+4, width: self.frame.width-160, height: 18)).font(UIFont.theme.labelMedium).backgroundColor(.clear).textAlignment(.center) 23 | }() 24 | 25 | private lazy var UIKit_version: UILabel = { 26 | UILabel(frame: CGRect(x: 80, y: self.demo_version.frame.maxY+4, width: self.frame.width-160, height: 18)).font(UIFont.theme.labelMedium).backgroundColor(.clear).text("UIKit Version "+ChatUIKit_VERSION).textAlignment(.center) 27 | }() 28 | 29 | 30 | override init(frame: CGRect) { 31 | super.init(frame: frame) 32 | self.addSubViews([self.icon,self.applicationName,self.demo_version,self.UIKit_version]) 33 | self.demo_version.text = "SDK Version "+ChatClient.shared().version 34 | if let appName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String { 35 | self.applicationName.text = appName 36 | } else if let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String { 37 | self.applicationName.text = appName 38 | } 39 | Theme.registerSwitchThemeViews(view: self) 40 | self.switchTheme(style: Theme.style) 41 | } 42 | 43 | required init?(coder: NSCoder) { 44 | fatalError("init(coder:) has not been implemented") 45 | } 46 | 47 | } 48 | 49 | extension AboutEasemobHeader: ThemeSwitchProtocol { 50 | 51 | func switchTheme(style: ThemeStyle) { 52 | self.backgroundColor = style == .dark ? UIColor.theme.neutralColor1:UIColor.theme.neutralColor98 53 | self.applicationName.textColor(style == .dark ? UIColor.theme.neutralColor98:UIColor.theme.neutralColor1) 54 | self.demo_version.textColor(style == .dark ? UIColor.theme.neutralColor6:UIColor.theme.neutralColor5) 55 | self.UIKit_version.textColor(style == .dark ? UIColor.theme.neutralColor6:UIColor.theme.neutralColor5) 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/PhotoBrowser/ImagePreviewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagePreviewController.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/7/8. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | public class ImagePreviewController: UIViewController { 12 | 13 | private var cache = Dictionary.init(minimumCapacity: 20) 14 | 15 | public weak var delegate: ImageBrowserProtocol! 16 | public var presentDuration: TimeInterval = 0.3 //显示动画时间 17 | public var dissmissDuration: TimeInterval = 0.3 //隐藏动画时间 18 | public var selectedIndex = 0 //当前显示图片的序号 19 | var collectionView: UICollectionView! 20 | public var pageControl: UIPageControl! 21 | private let transDelegate = ImagePreviewTransitionDelegate() 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | public init(with delegate: ImageBrowserProtocol) { 28 | super.init(nibName: nil, bundle: nil) 29 | self.delegate = delegate 30 | self.transitioningDelegate = transDelegate 31 | self.modalPresentationStyle = .overFullScreen 32 | self.modalTransitionStyle = .crossDissolve 33 | } 34 | 35 | override public func viewDidLoad() { 36 | 37 | super.viewDidLoad() 38 | self.view.backgroundColor = UIColor.black 39 | self.view.clipsToBounds = true 40 | 41 | let layout = UICollectionViewFlowLayout.init() 42 | layout.itemSize = UIScreen.main.bounds.size 43 | layout.scrollDirection = .horizontal 44 | layout.minimumLineSpacing = 10 45 | var frame = UIScreen.main.bounds 46 | frame.size.width += 10.0 47 | collectionView = UICollectionView.init(frame: frame, collectionViewLayout: layout) 48 | collectionView.contentInset = UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 10.0) 49 | collectionView.register(ImagePreviewCell.self, forCellWithReuseIdentifier: "cell") 50 | collectionView.delegate = self 51 | collectionView.dataSource = self 52 | collectionView.backgroundColor = UIColor.clear 53 | collectionView.isPagingEnabled = true 54 | collectionView.showsHorizontalScrollIndicator = false 55 | collectionView.decelerationRate = .normal 56 | self.view.addSubview(collectionView) 57 | collectionView.selectItem(at: IndexPath.init(item: selectedIndex, section: 0), animated: false, scrollPosition: .left) 58 | 59 | pageControl = UIPageControl.init(frame: CGRect.init(x: 0, y: ScreenHeight - 30, width: ScreenWidth, height: 20)) 60 | pageControl.numberOfPages = self.delegate.numberOfPhotos(with: self) 61 | pageControl.currentPage = selectedIndex 62 | pageControl.isUserInteractionEnabled = false 63 | self.view.addSubview(pageControl) 64 | pageControl.isHidden = delegate.numberOfPhotos(with: self) == 1 65 | 66 | } 67 | 68 | deinit { 69 | } 70 | 71 | 72 | 73 | @objc func disAction() { 74 | self.dismiss(animated: true, completion: nil) 75 | } 76 | 77 | } 78 | 79 | extension ImagePreviewController: UICollectionViewDataSource, UICollectionViewDelegate { 80 | 81 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 82 | return self.delegate.numberOfPhotos(with: self) 83 | } 84 | 85 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 86 | var photo = cache[indexPath.item] 87 | if photo == nil { 88 | photo = self.delegate.photo(of: indexPath.item, with: self) 89 | cache[indexPath.item] = photo 90 | } 91 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ImagePreviewCell 92 | cell.setPhoto(photo: photo) 93 | cell.index = indexPath.item 94 | cell.browser = self 95 | return cell 96 | } 97 | 98 | public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 99 | 100 | let page = Int(scrollView.contentOffset.x / (ScreenWidth + 10)) 101 | pageControl.currentPage = page 102 | 103 | } 104 | 105 | public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 106 | 107 | let page = Int(scrollView.contentOffset.x / (ScreenWidth + 10)) 108 | selectedIndex = page 109 | pageControl.currentPage = selectedIndex 110 | delegate.didDisplayPhoto?(at: page, with: self) 111 | 112 | } 113 | 114 | } 115 | 116 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/PhotoBrowser/ImagePreviewTransitionDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagePreviewTransitionDelegate.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/7/8. 6 | // 7 | 8 | import UIKit 9 | 10 | 11 | public class ImagePreviewTransitionDelegate: NSObject, UIViewControllerTransitioningDelegate { 12 | 13 | private var isPresented = true 14 | 15 | public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 16 | self.isPresented = true 17 | return self 18 | } 19 | 20 | public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 21 | self.isPresented = false 22 | return self 23 | } 24 | 25 | 26 | 27 | } 28 | 29 | 30 | extension ImagePreviewTransitionDelegate: UIViewControllerAnimatedTransitioning { 31 | 32 | public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 33 | return 0.5 34 | } 35 | 36 | public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 37 | 38 | if isPresented { 39 | animationForPresented(using: transitionContext) 40 | } else { 41 | animationForDissmissed(using: transitionContext) 42 | } 43 | 44 | } 45 | 46 | func animationForPresented(using transitionContext: UIViewControllerContextTransitioning) { 47 | 48 | let toView = transitionContext.view(forKey: .to)! 49 | transitionContext.containerView.addSubview(toView) 50 | let toController = transitionContext.viewController(forKey: .to) as! ImagePreviewController 51 | let originalView = toController.delegate.photo(of: toController.selectedIndex, with: toController).originalView 52 | guard originalView != nil else { 53 | transitionContext.completeTransition(true) 54 | return 55 | } 56 | 57 | let blackView = UIView.init(frame: UIScreen.main.bounds) 58 | blackView.backgroundColor = UIColor.black 59 | transitionContext.containerView.addSubview(blackView) 60 | let newRect = originalView!.convert(originalView!.bounds, to: nil) 61 | let imageView = UIImageView.init(frame: newRect) 62 | imageView.image = originalView?.image! 63 | imageView.contentMode = originalView!.contentMode 64 | transitionContext.containerView.addSubview(imageView) 65 | 66 | UIView.animate(withDuration: toController.presentDuration, animations: { 67 | 68 | imageView.frame = UIScreen.main.bounds 69 | imageView.contentMode = .scaleAspectFit 70 | 71 | }) { (finish) in 72 | 73 | imageView.removeFromSuperview() 74 | blackView.removeFromSuperview() 75 | transitionContext.completeTransition(true) 76 | 77 | } 78 | 79 | } 80 | 81 | func animationForDissmissed(using transitionContext: UIViewControllerContextTransitioning) { 82 | 83 | let fromController = transitionContext.viewController(forKey: .from) as! ImagePreviewController 84 | let originalView = fromController.delegate.photo(of: fromController.selectedIndex, with: fromController).originalView 85 | guard originalView != nil else { 86 | transitionContext.completeTransition(true) 87 | return 88 | } 89 | 90 | let cell = fromController.collectionView.cellForItem(at: IndexPath.init(item: fromController.selectedIndex, section: 0)) as! ImagePreviewCell 91 | let showView = cell.imageView! 92 | transitionContext.containerView.addSubview(showView) 93 | let newRect = originalView!.convert(originalView!.bounds, to: nil) 94 | UIView.animate(withDuration: fromController.dissmissDuration, animations: { 95 | 96 | showView.frame = newRect 97 | fromController.view.backgroundColor = UIColor.clear 98 | 99 | }) { (finish) in 100 | transitionContext.completeTransition(true) 101 | } 102 | 103 | } 104 | 105 | } 106 | 107 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/PhotoBrowser/PreviewImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatPhoto.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/7/8. 6 | // 7 | 8 | import UIKit 9 | 10 | @objcMembers final public class PreviewImage: NSObject { 11 | public var image: UIImage? 12 | public var urlString: String? 13 | public var placeholderImage : UIImage? 14 | public var originalView: UIImageView? 15 | 16 | public init(image: UIImage, originalView: UIImageView? = nil) { 17 | super.init() 18 | self.image = image 19 | self.originalView = originalView 20 | } 21 | 22 | public init(urlString: String, placeholderImage: UIImage? = nil, originalView: UIImageView? = nil) { 23 | super.init() 24 | self.urlString = urlString 25 | self.placeholderImage = placeholderImage 26 | self.originalView = originalView 27 | } 28 | } 29 | 30 | @objc public protocol ImageBrowserProtocol { 31 | 32 | //需要显示图片的数量 33 | func numberOfPhotos(with browser: ImagePreviewController) -> Int 34 | //序号对应显示的图片 35 | func photo(of index: Int, with browser: ImagePreviewController) -> PreviewImage 36 | 37 | //当前显示的图片序号 38 | @objc optional func didDisplayPhoto(at index: Int, with browser: ImagePreviewController) -> Void 39 | //长按序号为index的图片,可以自己在这里添加一些菜单操作 40 | @objc optional func didLongPressPhoto(at index: Int, with browser: ImagePreviewController) -> Void 41 | 42 | } 43 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/5. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | import SwiftFFDBHotFix 11 | 12 | final class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | @UserDefault("EaseChatDemoDarkMode", defaultValue: false) var darkMode: Bool 15 | 16 | @UserDefault("EaseChatDemoPreferencesLanguage", defaultValue: "zh-Hans") var language: String 17 | 18 | var window: UIWindow? 19 | 20 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 21 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 22 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 23 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 24 | guard let windowScene = (scene as? UIWindowScene) else { return } 25 | //设置整个UIKit中的语言 目前只支持中英文 26 | Appearance.ease_chat_language = LanguageType(rawValue: self.language) ?? .Chinese 27 | self.window = nil 28 | self.window = UIWindow(windowScene: windowScene) 29 | self.window?.backgroundColor = .black 30 | EaseChatProfile.registerTable()//使用三方数据库,将模型注册成为表 31 | self.chooseRootViewController() 32 | self.window?.makeKeyAndVisible() 33 | self.switchTheme() 34 | NotificationCenter.default.addObserver(self, selector: #selector(loadMain), name: Notification.Name(loginSuccessfulSwitchMainPage), object: nil) 35 | NotificationCenter.default.addObserver(self, selector: #selector(loadLogin), name: Notification.Name(backLoginPage), object: nil) 36 | } 37 | 38 | private func switchTheme() { 39 | Theme.registerSwitchThemeViews(view: self) 40 | Theme.switchTheme(style: self.darkMode ? .dark:.light) 41 | self.switchTheme(style: self.darkMode ? .dark:.light) 42 | } 43 | 44 | private func chooseRootViewController() { 45 | if !ChatClient.shared().isAutoLogin { 46 | self.window?.rootViewController = LoginViewController() 47 | } else { 48 | guard let userId = ChatClient.shared().currentUsername,!userId.isEmpty else { 49 | self.window?.rootViewController = LoginViewController() 50 | return 51 | } 52 | self.loadCache() 53 | self.window?.rootViewController = MainViewController() 54 | } 55 | } 56 | 57 | private func loadCache() { 58 | if let profiles = EaseChatProfile.select(where: nil) as? [EaseChatProfile] { 59 | for profile in profiles { 60 | if let conversation = ChatClient.shared().chatManager?.getConversationWithConvId(profile.id) { 61 | if conversation.type == .chat { 62 | ChatUIKitContext.shared?.userCache?[profile.id] = profile 63 | } else { 64 | ChatUIKitContext.shared?.groupCache?[profile.id] = profile 65 | } 66 | } 67 | if profile.id == ChatClient.shared().currentUsername ?? "" { 68 | ChatUIKitContext.shared?.currentUser = profile 69 | ChatUIKitContext.shared?.userCache?[profile.id] = profile 70 | } 71 | } 72 | } 73 | 74 | } 75 | 76 | func sceneDidDisconnect(_ scene: UIScene) { 77 | // Called as the scene is being released by the system. 78 | // This occurs shortly after the scene enters the background, or when its session is discarded. 79 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 80 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 81 | } 82 | 83 | func sceneDidBecomeActive(_ scene: UIScene) { 84 | // Called when the scene has moved from an inactive state to an active state. 85 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 86 | } 87 | 88 | func sceneWillResignActive(_ scene: UIScene) { 89 | // Called when the scene will move from an active state to an inactive state. 90 | // This may occur due to temporary interruptions (ex. an incoming phone call). 91 | } 92 | 93 | func sceneWillEnterForeground(_ scene: UIScene) { 94 | // Called as the scene transitions from the background to the foreground. 95 | // Use this method to undo the changes made on entering the background. 96 | self.switchTheme(style: self.darkMode == false ? .light:.dark) 97 | ChatClient.shared().applicationWillEnterForeground(UIApplication.shared) 98 | } 99 | 100 | func sceneDidEnterBackground(_ scene: UIScene) { 101 | // Called as the scene transitions from the foreground to the background. 102 | // Use this method to save data, release shared resources, and store enough scene-specific state information 103 | // to restore the scene back to its current state. 104 | ChatClient.shared().applicationDidEnterBackground(UIApplication.shared) 105 | } 106 | 107 | @objc private func loadMain() { 108 | DispatchQueue.main.async { 109 | if self.window?.rootViewController is LoginViewController { 110 | self.window?.rootViewController = MainViewController() 111 | } 112 | } 113 | } 114 | 115 | @objc private func loadLogin() { 116 | DispatchQueue.main.async { 117 | self.window?.rootViewController = LoginViewController() 118 | } 119 | } 120 | 121 | } 122 | 123 | extension SceneDelegate: ThemeSwitchProtocol { 124 | func switchTheme(style: ThemeStyle) { 125 | self.window?.backgroundColor = style == .dark ? UIColor.theme.neutralColor1 : UIColor.theme.neutralColor98 126 | UIApplication.shared.windows.forEach { $0.overrideUserInterfaceStyle = (style == .dark ? .dark:.light) } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Utils/DemoLanguage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoLanguage.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/7. 6 | // 7 | 8 | import Foundation 9 | import EaseChatUIKit 10 | 11 | struct DemoLanguage { 12 | 13 | static let shared = DemoLanguage() 14 | 15 | public static func localValue(key: String) -> String { 16 | DemoLanguage.shared.localValue(key) 17 | } 18 | 19 | private func localValue(_ key: String) -> String { 20 | guard var lang = NSLocale.preferredLanguages.first else { return Bundle.main.bundlePath } 21 | if !Appearance.ease_chat_language.rawValue.isEmpty { 22 | lang = Appearance.ease_chat_language.rawValue 23 | } 24 | 25 | let path = Bundle.main.path(forResource: lang, ofType: "lproj") ?? "" 26 | let pathBundle = Bundle(path: path) ?? .main 27 | let value = pathBundle.localizedString(forKey: key, value: nil, table: nil) 28 | return value 29 | } 30 | 31 | static func chineseLanguage() -> Bool { 32 | guard let lang = NSLocale.preferredLanguages.first else { return false } 33 | if lang.contains("zh") { 34 | return true 35 | } else { 36 | return false 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Utils/EaseMobHUD.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EaseMobHUD.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/29. 6 | // 7 | 8 | import UIKit 9 | import EaseChatUIKit 10 | 11 | final class EaseMobHUD: UIView { 12 | static let shared = EaseMobHUD() 13 | 14 | private var containerView: UIView? 15 | private var loadingLabel: UILabel? 16 | 17 | init() { 18 | super.init(frame: .zero) 19 | setupHUD() 20 | } 21 | 22 | required init?(coder: NSCoder) { 23 | fatalError("init(coder:) has not been implemented") 24 | } 25 | 26 | private func setupHUD() { 27 | 28 | let containerView = UIView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: ScreenHeight)) 29 | containerView.backgroundColor = UIColor.black.withAlphaComponent(0.2) 30 | containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 31 | 32 | let toastView = UIVisualEffectView(effect: UIBlurEffect(style: Theme.style == .dark ? .light:.dark)).cornerRadius(.medium) 33 | toastView.alpha = 0 34 | toastView.backgroundColor = Theme.style == .dark ? UIColor.theme.barrageLightColor3:UIColor.theme.barrageDarkColor3 35 | toastView.translatesAutoresizingMaskIntoConstraints = false 36 | let content = "Loading...".chat.localize 37 | let size = content.chat.sizeWithText(font: UIFont.theme.bodyMedium, size: CGSize(width: ScreenWidth-80, height: 999)) 38 | containerView.addSubview(toastView) 39 | containerView.bringSubviewToFront(toastView) 40 | var toastWidth = size.width+40 41 | if toastWidth >= ScreenWidth-80 { 42 | toastWidth = ScreenWidth - 80 43 | } 44 | NSLayoutConstraint.activate([ 45 | toastView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), 46 | toastView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor), 47 | toastView.widthAnchor.constraint(lessThanOrEqualToConstant: containerView.frame.width-80), 48 | toastView.heightAnchor.constraint(greaterThanOrEqualToConstant: size.height+16) 49 | ]) 50 | 51 | let label = UILabel().text(content).textColor(UIColor.theme.neutralColor98).textAlignment(.center).numberOfLines(0).backgroundColor(.clear).text("Loading...".chat.localize) 52 | label.translatesAutoresizingMaskIntoConstraints = false 53 | toastView.contentView.addSubview(label) 54 | 55 | NSLayoutConstraint.activate([ 56 | label.leadingAnchor.constraint(equalTo: toastView.leadingAnchor, constant: 10), 57 | label.trailingAnchor.constraint(equalTo: toastView.trailingAnchor, constant: -10), 58 | label.topAnchor.constraint(equalTo: toastView.topAnchor, constant: 8), 59 | label.bottomAnchor.constraint(equalTo: toastView.bottomAnchor, constant: -8) 60 | ]) 61 | 62 | UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: { 63 | toastView.alpha = 1 64 | }, completion: { (finished) in 65 | if finished { 66 | DispatchQueue.main.asyncAfter(deadline: .now()+1.5) { 67 | toastView.removeFromSuperview() 68 | } 69 | } 70 | }) 71 | 72 | self.containerView = containerView 73 | } 74 | 75 | } 76 | 77 | 78 | extension EaseMobHUD { 79 | 80 | func show(window: UIWindow) { 81 | DispatchQueue.main.async { 82 | guard let containerView = self.containerView else { return } 83 | 84 | window.addSubview(containerView) 85 | } 86 | } 87 | 88 | func dismiss() { 89 | DispatchQueue.main.async { 90 | self.containerView?.removeFromSuperview() 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Utils/EasemobBusinessApi.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasemobBusinessApi.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/5. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum EasemobBusinessApi { 11 | case login(Void) 12 | case deregister(String) 13 | case verificationCode(String) 14 | case refreshIMToken(Void) 15 | case autoDestroyGroup(String) 16 | case fetchGroupAvatar(String) 17 | case fetchRTCToken(String,String) 18 | case addFriendByPhoneNumber(String,String) 19 | case mirrorCallUserIdToChatUserId(String) 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/Utils/GCDTimer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GCDTimer.swift 3 | // EaseChatDemo 4 | // 5 | // Created by 朱继超 on 2024/3/5. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | public protocol GCDTimer { 12 | // 启动 13 | func resume() 14 | // 暂停 15 | func suspend() 16 | // 取消 17 | func cancel() 18 | } 19 | 20 | public class GCDTimerMaker { 21 | 22 | static func exec(_ task: (() -> ())?, interval: Int, repeats: Bool = true, async: Bool = true) -> GCDTimer? { 23 | 24 | guard let _ = task else { 25 | return nil 26 | } 27 | 28 | return TimerMaker(task, 29 | deadline: .now(), 30 | repeating: repeats ? .seconds(interval):.never, 31 | async: async) 32 | 33 | } 34 | } 35 | 36 | private class TimerMaker: GCDTimer { 37 | 38 | /// 当前 Timer 运行状态 39 | enum TimerState { 40 | case running 41 | case stopped 42 | case suspended 43 | } 44 | 45 | private var state: TimerState = .stopped 46 | private var timer: DispatchSourceTimer? 47 | private let stateQueue = DispatchQueue(label: "com.timer.state.queue") // 用于保护状态访问 48 | 49 | convenience init( 50 | _ exce: (() -> Void)?, 51 | deadline: DispatchTime, 52 | repeating interval: DispatchTimeInterval = .never, 53 | leeway: DispatchTimeInterval = .seconds(0), 54 | async: Bool = true 55 | ) { 56 | self.init() 57 | 58 | let queue = async ? DispatchQueue.global() : DispatchQueue.main 59 | timer = DispatchSource.makeTimerSource(queue: queue) 60 | 61 | timer?.schedule(deadline: deadline, repeating: interval, leeway: leeway) 62 | 63 | timer?.setEventHandler { 64 | exce?() 65 | } 66 | } 67 | 68 | /// 开始计时器 69 | func resume() { 70 | stateQueue.sync { 71 | guard state != .running else { return } 72 | state = .running 73 | timer?.resume() 74 | } 75 | } 76 | 77 | /// 暂停计时器 78 | func suspend() { 79 | stateQueue.sync { 80 | guard state != .suspended else { return } 81 | state = .suspended 82 | timer?.suspend() 83 | } 84 | } 85 | 86 | /// 停止并释放计时器 87 | func cancel() { 88 | stateQueue.sync { 89 | guard let timer = timer,state != .stopped else { return } 90 | state = .stopped 91 | if timer.isCancelled { return } 92 | timer.cancel() 93 | 94 | // 清空事件处理程序以释放强引用 95 | timer.setEventHandler {} 96 | 97 | // 延迟释放 DispatchSourceTimer 98 | self.timer = nil 99 | } 100 | } 101 | 102 | deinit { 103 | cancel() // 确保释放时安全地清理计时器 104 | } 105 | } 106 | 107 | 108 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | EaseChatDemo 4 | 5 | Created by 朱继超 on 2024/3/5. 6 | 7 | */ 8 | "Me" = "Me"; 9 | "Chats" = "Chats"; 10 | "Contacts" = "Contacts"; 11 | " and " = " and "; 12 | "Login" = "Log In"; 13 | "Logout" = "Log Out"; 14 | "Deregister" = "Deregister"; 15 | "Deregister Alert" = "After deregistration, the account cannot be restored, and you will not be able to add or send any messages using this account. The personal information recorded in this account (including but not limited to avatars, user names, etc.) and information data related to the account (including but not limited to contacts, created sessions, session details, etc.) will not be able to be retrieved."; 16 | "Service" = "Service"; 17 | "Setting" = "Settings"; 18 | "Privacy Policy" = "Privacy Policy"; 19 | "Get Code" = "Get Code"; 20 | "Get After" = "Get After"; 21 | "Mobile Number" = "Phone Number"; 22 | "PinCodePlaceHolder" = "Verification code"; 23 | "PhoneError" = "Please enter the correct mobile number."; 24 | "AgreeProtocol" = "Agree to Easemob Terms & Privacy"; 25 | "PinCodeError" = "Please enter the correct verification code"; 26 | "Login Easemob Chat" = "Easemob IM"; 27 | "Please tick to agree" = "Please tick to agree "; 28 | 29 | "Official Website" = "Official Website"; 30 | "Hotline" = "Hotline"; 31 | "Business Cooperation" = "Business Cooperation"; 32 | "Channel Cooperation" = "Channel Cooperation"; 33 | "Suggestions" = "Suggestions"; 34 | 35 | "Avatar" = "Avatar"; 36 | "Nickname" = "Nickname"; 37 | 38 | "dark_mode" = "Dark Mode"; 39 | "switch_theme" = "Switch Theme"; 40 | "color_setting" = "Color Setting"; 41 | "feature_switch" = "Feature Switch"; 42 | "language_setting" = "Language Setting"; 43 | 44 | 45 | "Chinese" = "Chinese"; 46 | "English" = "English"; 47 | 48 | "Switch Language" = "Switch Language"; 49 | "Switch Theme" = "Switch Theme"; 50 | "Terminal Alert" = "The application will be closed, are you sure?"; 51 | "Classic" = "Classic"; 52 | "Smart" = "Modern"; 53 | "primary hue" = "Primary Hue"; 54 | "secondary hue" = "Secondary Hue"; 55 | "error hue" = "Error Hue"; 56 | "neutralHue" = "Neutral Hue"; 57 | "neutral special hue" = "Neutral Special Hue"; 58 | 59 | "GetPinCodeSuccessful" = "Verification code sent successfully"; 60 | 61 | "PasswordError" = "Please enter the correct password."; 62 | "UserIdError" = "Please enter the correct user id."; 63 | 64 | "EeaseMobID" = "EeaseMob ID"; 65 | "Password" = "Password"; 66 | "General" = "General"; 67 | "Notification" = "Message Notifications"; 68 | "About" = "About"; 69 | "message_translate" = "Translation"; 70 | "group_topic" = "Thread"; 71 | "message_reaction" = "Reaction"; 72 | "otherDevice" = "Already processed on other end"; 73 | "remoteBusy" = "The other party is busy"; 74 | "refuseCall" = "Call rejected"; 75 | "cancelCall" = "You have canceled the call"; 76 | "callCancel" = "Call canceled"; 77 | "remoteNoResponse" = "The other party did not answer"; 78 | "noResponse" = "Not answered"; 79 | 80 | "callendPrompt" = "The call has ended. Call duration:"; 81 | "Audio Call" = "Audio Call"; 82 | "Video Call" = "Video Call"; 83 | " start a multi call" = " start a multi call"; 84 | "contact_details_button_remark" = "Remark"; 85 | "Offline Message Push" = "Offline Message Push"; 86 | "Notification Alert" = "After turning off, you will no longer receive any message notifications except for messages where you are @mentioned in the group."; 87 | 88 | "Confirm Logout" = "Confirm Logout?"; 89 | "Modify Remark" = "Modify Remark"; 90 | "Modify Nickname" = "Modify nickname"; 91 | "Modify failed" = "Modify failed"; 92 | 93 | "message_translate_description" = "Long press a message translate into system language"; 94 | "group_topic_description" = "Create a thread from a message within the group"; 95 | "message_reaction_description" = "Long press a message to add emoji reactions"; 96 | 97 | "Server Config" = "Server Config"; 98 | "Clean successful!" = "Clear successful"; 99 | "agreeFriendRequest success" = "Friend request accepted"; 100 | "Debug Log" = "Debug Log"; 101 | "Privacy" = "Privacy"; 102 | "block_list" = "Block List"; 103 | "Target Language" = "Target Language"; 104 | "After setting, long-pressing a message will translate it into the specified target language." = "After setting, long-pressing a message will translate it into the specified target language."; 105 | "online_status" = "User Status"; 106 | "Custom Status" = "Custom"; 107 | "Personal Info" = "Profile"; 108 | "Away" = "Away"; 109 | "Busy" = "Busy"; 110 | "The length of the custom status should be less than 20 characters" = "The length of the custom status should be less than 20 characters"; 111 | "Do Not Disturb" = "Do Not Disturb"; 112 | "Offline" = "Offline"; 113 | 114 | "typing_indicator" = "Typing Indicator"; 115 | "translate_language_setting" = "Target Language"; 116 | "translate_language_setting_alert" = "After setting, long-pressing a message will translate it into the specified target language."; 117 | "Typing Alert" = "Turn on,other party'll saw your typing"; 118 | "Block Alert" = "Enable the friend blacklist feature"; 119 | 120 | "Account" = "Account"; 121 | "The user does not exist" = "The user does not exist"; 122 | "Friend request sent" = "Friend request sent"; 123 | "Group disbanded" = "Group disbanded"; 124 | "Save Image" = "Save Image"; 125 | 126 | "contactID" = "Add contacts by phone number or ID."; 127 | 128 | "long_press_style" = "Long press the message menu"; 129 | "attachment_menu_style" = "Send attachment menu"; 130 | 131 | "style1" = "Style 1"; 132 | "style2" = "Style 2"; 133 | "style_preview" = "Style Preview"; 134 | 135 | "fraud_alert" = "请勿轻信任何关于汇款、中奖等信息,务必提高警惕,谨慎对待来自陌生号码的电话。如遇可疑情况,请及时向相关部门反馈并采取必要的防范措施。"; 136 | "Click report" = "点击举报"; 137 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/zh-Hans.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Powered by Easemob"; ObjectID = "ZOK-Th-nTI"; */ 3 | "ZOK-Th-nTI.text" = "Powered by Easemob"; 4 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | EaseChatDemo 4 | 5 | Created by 朱继超 on 2024/3/5. 6 | 7 | */ 8 | "Me" = "我"; 9 | "Chats" = "会话"; 10 | "Contacts" = "联系人"; 11 | " and " = "和"; 12 | "Login" = "登录"; 13 | "Logout" = "退出登录"; 14 | "Deregister" = "注销"; 15 | "Deregister Alert" = "注销后账户不可恢复,您将无法使用本账户添加、发送任何消息。本账户所记录的个人信息(包括不限于头像、用户名等)、与账户有关的信息数据(包括不限于联系人、创建的会话、会话详情等)都将无法找回。"; 16 | "Get Code" = "获取验证码"; 17 | "Service" = "《环信服务条款》"; 18 | "Setting" = "设置"; 19 | "Privacy Policy" = "《环信隐私协议》"; 20 | "Mobile Number" = "手机号"; 21 | "PinCodePlaceHolder" = "验证码"; 22 | "PhoneError" = "请输入正确的电话号码."; 23 | "AgreeProtocol" = "请勾选同意协议"; 24 | "PinCodeError" = "请输入正确的验证码"; 25 | "Login Easemob Chat" = "环信IM"; 26 | "Please tick to agree" = "请勾选同意"; 27 | 28 | 29 | "Official Website" = "访问官网"; 30 | "Hotline" = "服务热线"; 31 | "Business Cooperation" = "商务合作"; 32 | "Channel Cooperation" = "渠道合作"; 33 | "Suggestions" = "投诉建议"; 34 | 35 | "Avatar" = "头像"; 36 | "Nickname" = "昵称"; 37 | 38 | 39 | 40 | "dark_mode" = "暗黑模式"; 41 | "switch_theme" = "切换主题"; 42 | "color_setting" = "颜色设置"; 43 | "feature_switch" = "特性开关"; 44 | "language_setting" = "语言设置"; 45 | "Chinese" = "中文"; 46 | "English" = "英文"; 47 | 48 | "Switch Language" = "切换语言"; 49 | "Switch Theme" = "切换主题"; 50 | "Terminal Alert" = "应用将会关闭,确定?"; 51 | 52 | "Classic" = "经典"; 53 | "Smart" = "现代"; 54 | 55 | "primary hue" = "主题色色相"; 56 | "secondary hue" = "成功色色相"; 57 | "error hue" = "警示色色相"; 58 | "neutralHue" = "中性色色相"; 59 | "neutral special hue" = "特殊中性色色相"; 60 | "GetPinCodeSuccessful" = "获取成功"; 61 | "PasswordError" = "请输入正确的密码"; 62 | 63 | "UserIdError" = "请输入正确的用户ID"; 64 | 65 | "EeaseMobID" = "环信ID"; 66 | "Password" = "密码"; 67 | 68 | "Personal Info" = "个人信息"; 69 | "General" = "通用"; 70 | "Notification" = "消息通知"; 71 | "About" = "关于"; 72 | 73 | "message_translate" = "消息翻译"; 74 | "group_topic" = "群组话题"; 75 | "message_reaction" = "表情回应"; 76 | "otherDevice" = "已在其他端处理"; 77 | "remoteBusy" = "对方忙"; 78 | "refuseCall" = "拒绝通话"; 79 | "cancelCall" = "您已取消呼叫"; 80 | "callCancel" = "通话已取消"; 81 | "remoteNoResponse" = "对方超时未响应"; 82 | "noResponse" = "未接听"; 83 | "callendPrompt" = "通话已结束,通话时长:"; 84 | 85 | "Audio Call" = "音频通话"; 86 | "Video Call" = "视频通话"; 87 | " start a multi call" = "发起多人通话"; 88 | "contact_details_button_remark" = "备注"; 89 | "Offline Message Push" = "消息离线推送"; 90 | 91 | "Notification Alert" = "关闭后,除群组内被@的消息,将不再接收任何消息推送"; 92 | "Confirm Logout" = "确定退出登录?"; 93 | "Modify Remark" = "修改备注"; 94 | "Modify Nickname" = "修改昵称"; 95 | "Modify failed" = "修改失败"; 96 | "Get After" = "重新获取"; 97 | 98 | 99 | "message_translate_description" = "通过长按将消息翻译为系统语言"; 100 | "group_topic_description" = "通过群组内的一条消息创建一个话题"; 101 | "message_reaction_description" = "通过长按消息添加表情回应"; 102 | 103 | "Server Config" = "服务器配置"; 104 | "Clean successful!" = "清除成功"; 105 | 106 | "agreeFriendRequest success" = "好友请求已同意"; 107 | "Debug Log" = "调试日志"; 108 | "Privacy" = "隐私"; 109 | "block_list" = "黑名单"; 110 | "Target Language" = "目标语言"; 111 | "After setting, long-pressing a message will translate it into the specified target language." = "设置后,长按消息将翻译为指定的目标语言"; 112 | "online_status" = "用户状态"; 113 | "Custom Status" = "自定义"; 114 | "Away" = "离开"; 115 | "Busy" = "忙碌"; 116 | "The length of the custom status should be less than 20 characters" = "自定义状态长度应该小于20个字符"; 117 | "Do Not Disturb" = "勿扰"; 118 | "Online" = "在线"; 119 | "Offline" = "离线"; 120 | 121 | "typing_indicator" = "输入状态"; 122 | "translate_language_setting" = "翻译目标语言"; 123 | 124 | "translate_language_setting_alert" = "设置后,长按消息将翻译为指定的目标语言"; 125 | 126 | "Typing Alert" = "开启后,对方将看到您的输入状态"; 127 | "Block Alert" = "开启好友黑名单功能"; 128 | 129 | "Account" = "账号"; 130 | "The user does not exist" = "用户不存在"; 131 | "Friend request sent" = "好友请求已发送"; 132 | "Group disbanded" = "群组已解散"; 133 | "Save Image" = "保存图片"; 134 | 135 | "contactID" = "通过手机号或ID添加联系人"; 136 | 137 | "long_press_style" = "长按消息菜单"; 138 | "attachment_menu_style" = "发送附件菜单"; 139 | "style1" = "样式1"; 140 | "style2" = "样式2"; 141 | "style_preview" = "样式预览"; 142 | "fraud_alert" = "请勿轻信任何关于汇款、中奖等信息,务必提高警惕,谨慎对待来自陌生号码的电话。如遇可疑情况,请及时向相关部门反馈并采取必要的防范措施。"; 143 | "Click report" = "点击举报"; 144 | -------------------------------------------------------------------------------- /EaseChatDemo/EaseChatDemo/zh-Hans.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /EaseChatDemo/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '14.0' 3 | 4 | target 'EaseChatDemo' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for EaseChatDemo 9 | pod 'EaseChatUIKit', '~> 4.14.0' 10 | pod 'EaseCallKit' 11 | pod 'KakaJSON', '~> 1.1.2' 12 | pod 'SwiftFFDBHotFix' 13 | pod 'FMDB','2.7.11' 14 | #如果HyphenateChat使用4.11.0版本需要下面pre_install这个脚本,否则不需要 15 | pre_install do |installer| 16 | # 定义 HypheanteChat framework 的路径 17 | rtc_pod_path = File.join(installer.sandbox.root, 'AgoraRtcEngine_iOS') 18 | 19 | # aosl.xcframework 的完整路径 20 | aosl_xcframework_path = File.join(rtc_pod_path, 'aosl.xcframework') 21 | 22 | # 检查文件是否存在,如果存在则删除 23 | if File.exist?(aosl_xcframework_path) 24 | puts "Deleting aosl.xcframework from #{aosl_xcframework_path}" 25 | FileUtils.rm_rf(aosl_xcframework_path) 26 | else 27 | puts "aosl.xcframework not found, skipping deletion." 28 | end 29 | end 30 | 31 | post_install do |installer| 32 | installer.generated_projects.each do |project| 33 | 34 | project.targets.each do |target| 35 | target.build_configurations.each do |config| 36 | config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] = "14.0" 37 | config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64" 38 | config.build_settings["DEVELOPMENT_TEAM"] = "JC854K845H" 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 产品介绍 2 | 3 | 环信新版本UIKit Demo:打造卓越聊天体验的强大工具 4 | 5 | 全面功能,产品化体验 6 | 7 | 环信UIKit Demo为您提供全面的聊天功能,助力您轻松构建功能强大、产品化的聊天体验。从基本的文字消息到高级的群组互动,我们的Demo涵盖了所有市场通用能力,让您能够满足用户的各种聊天需求。 8 | 9 | 开箱即用,快速集成 10 | 11 | 我们的Demo经过精心设计,可轻松集成到您的现有应用程序中。清晰的代码结构和详尽的文档让您能够快速上手,无需繁琐的配置和开发工作。 12 | 13 | 应用服务器示例代码,简化集成 14 | 15 | 为了进一步简化集成过程,我们提供了完整的应用服务器示例代码,展示了如何将您的应用程序连接到环信IM后端。这将帮助您轻松实现聊天功能的部署和运行。 16 | 17 | 功能亮点: 18 | 19 | 流畅的实时消息传递 20 | 语音和视频通话 21 | 文件共享 22 | 群组聊天 23 | 线程讨论 24 | 群组成员管理 25 | 消息提醒 26 | 可定制界面 27 | 预配置的聊天功能 28 | 应用服务器示例代码 29 | 30 | 立即体验环信新版本UIKit Demo,开始构建您的梦想聊天应用程序吧! 31 | 32 | # 产品体验 33 | 34 | ![](./demo.png) 35 | 36 | # 前置环境需求 37 | 38 | - Xcode 15.0及以上版本 原因是UIKit中使用了部分检测音频AVAudioApplication api适配iOS17以上系统 39 | - 最低支持系统:iOS 14.0 40 | - 请确保您的项目已设置有效的开发者签名 41 | 42 | 可以使用 CocoaPods 安装 EaseChatUIKit 作为 Xcode 项目的依赖项。 43 | 44 | 在podfile中添加如下依赖 45 | 46 | ```ruby 47 | source 'https://github.com/CocoaPods/Specs.git' 48 | platform :ios, '14.0' 49 | 50 | target 'YourTarget' do 51 | use_frameworks! 52 | 53 | pod 'EaseChatUIKit' 54 | end 55 | 56 | post_install do |installer| 57 | installer.pods_project.targets.each do |target| 58 | target.build_configurations.each do |config| 59 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0' 60 | end 61 | end 62 | end 63 | ``` 64 | 65 | 然后cd到终端下podfile所在文件夹目录执行 66 | 67 | ``` 68 | pod install 69 | ``` 70 | 71 | >⚠️Xcode15编译报错 ```Sandbox: rsync.samba(47334) deny(1) file-write-create...``` 72 | 73 | > 解决方法: Build Setting里搜索 ```ENABLE_USER_SCRIPT_SANDBOXING```把```User Script Sandboxing```改为```NO``` 74 | 75 | # 跑通Demo 76 | 77 | 1. [注册环信应用](https://doc.easemob.com/product/enable_and_configure_IM.html) 78 | 79 | 2. 将Appkey填入`PublicDefines.swift`文件中的`AppKey`中 80 | 81 | 3. 需要将[服务端源码](https://github.com/easemob/easemob-im-app-server/tree/dev-demo)部署后填入`PublicDefines.swift`文件中的`ServerHost`中,手机号验证码暂时可以跳过,可以使用手机号后六位当验证码,服务端中的Appkey 要跟客户端的Appkey保持一致。Appserver主要提供了手机号验证码登录接口以及上传用户头像的接口,此接口主要的职能是根据用户的信息注册并生成EaseChatUIKit登录所需的token或者使用已注册的用户信息生成EaseChatUIKit登录所需的token,上传头像是一个普通的通用功能在此不过多赘述。 82 | 83 | 4. 点击运行至目标设备上(注意:不支持arm模拟器,需要选择Rosetta模拟器或者真机) 84 | 85 | # EaseChatUIKit在Demo中的使用 86 | 87 | ## 1. 初始化 88 | 89 | [详情参见](./EaseChatDemo/EaseChatDemo/AppDelegate.swift) 中 `didFinishLaunchingWithOptions`方法中步骤。 90 | 91 | ## 2. 登录 92 | 93 | [详情参见](./EaseChatDemo/EaseChatDemo/LoginViewController.swift)中`loginRequest`方法后续步骤 94 | 95 | ## 3. Provider使用及其最佳示例用法 96 | 97 | 如果您的App中已经有完备的用户体系以及可供展示的用户信息(例如头像昵称等。)可以实现EaseChatProfileProvider协议来提供给UIKit要展示的数据。 98 | 99 | 3.1 [Provider初始化详情参见](./EaseChatDemo/EaseChatDemo/Main/MainViewController.swift)中`viewDidLoad`方法中 100 | 101 | 3.2 实现Provider协议对`MainViewController`类的扩展参见下述示例代码 102 | 103 | ```Swift 104 | 4.10.0及其以上 105 | extension MainViewController: ChatUserProfileProvider,ChatGroupProfileProvider { 106 | 107 | } 108 | 4.10.0以下 109 | extension MainViewController: EaseProfileProvider,EaseGroupProfileProvider { 110 | 111 | } 112 | ``` 113 | 114 | 115 | ## 4.集成EaseChatUIKit中的类进行二次开发 116 | 117 | 4.1 如何继承EaseChatUIKit中的可自定义的类 118 | 119 | [参见IntegratedFromEaseChatUIKit文件夹](./EaseChatDemo/EaseChatDemo/IntegratedFromEaseChatUIKit)中 120 | 121 | 4.2 如何将继承EaseChatUIKit中子类注册进EaseChatUIKit中替换父类 122 | 123 | [详情参见](./EaseChatDemo/EaseChatDemo/AppDelegate.swift) 中 `didFinishLaunchingWithOptions`方法 124 | 125 | # Demo设计 126 | 浏览器中打开如下链接 127 | https://www.figma.com/community/file/1327193019424263350/chat-uikit-for-mobile 128 | 129 | 130 | # 已知问题 131 | 132 | 1. callkit 群聊呼叫用户会产生一条单聊消息,即便对方不是您的好友,后续会改进,也可以用户自己使用群聊中的定向消息自己做信令。 133 | 2. 会话列表、联系人列表是单独的模块,如果想要监听好友事件需要初始化ContactViewModel后调用registerListener方法监听。 134 | 3. UserProvider以及GroupProvider需要用户自己实现,用于获取用户的展示信息以及群组的简要展示信息,如果不实现默认用id以及默认头像。 135 | 4. 换设备或者多设备登录,漫游的会话列表,环信SDK中没有本地存储的群头像名称等显示信息,需要用户使用Provider提供给UIKit才能正常显示。 136 | 5. 由于Provider的机制是停止滚动或者第一页不满10条数据时触发,所以更新会话列表以及联系人列表UI显示的昵称头像需要滑动后Provider提供给UIKit数据后,UIKit会刷新UI。 137 | 138 | 6. 不支持arm模拟器,因为音频录制库使用libffmpeg的wav转amr的库。 139 | 140 | 141 | # Q&A 142 | 143 | 144 | 如有问题请联系环信技术支持或者发邮件到issue@easemob.com 145 | 146 | 147 | [推送角标后台更新以及推送达到率统计](https://doc.easemob.com/push/push_apns_deliver_statistics.html#_1%E3%80%81%E6%8E%A8%E9%80%81%E6%9C%8D%E5%8A%A1%E6%89%A9%E5%B1%95%E4%BB%8B%E7%BB%8D) 148 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-ios/a0a4834f535e0c31481a7c8f4e2ebcc69c2009c0/demo.png --------------------------------------------------------------------------------