From eec8deb03d486be6cba78282809df861c9bad1cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=96=84=E6=A0=8B?= Date: Mon, 19 Sep 2022 16:05:32 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9pod=20=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Podfile | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Podfile b/Podfile index 7ae8f3fe..55659da8 100644 --- a/Podfile +++ b/Podfile @@ -9,25 +9,25 @@ target 'app' do pod 'YXLogin', '1.0.0' #可选UI库 - pod 'NEContactUIKit', '9.2.7' - pod 'NEQChatUIKit', '9.2.7' - pod 'NEConversationUIKit', '9.2.7' - pod 'NEChatUIKit', '9.2.7' - pod 'NETeamUIKit', '9.2.7' + pod 'NEContactUIKit', '9.2.8' + pod 'NEQChatUIKit', '9.2.8' + pod 'NEConversationUIKit', '9.2.8' + pod 'NEChatUIKit', '9.2.8' + pod 'NETeamUIKit', '9.2.8' #可选Kit库(和UIKit对应) - pod 'NEContactKit', '9.2.7' - pod 'NEQChatKit', '9.2.7' - pod 'NEConversationKit', '9.2.7' - pod 'NEChatKit', '9.2.7' - pod 'NETeamKit', '9.2.7' + pod 'NEContactKit', '9.2.8' + pod 'NEQChatKit', '9.2.8' + pod 'NEConversationKit', '9.2.8' + pod 'NEChatKit', '9.2.8' + pod 'NETeamKit', '9.2.8' #基础kit库 - pod 'NECommonUIKit', '9.2.7' - pod 'NECommonKit', '9.2.7' - pod 'NECoreIMKit', '9.2.7' - pod 'NECoreKit', '9.2.7' + pod 'NECommonUIKit', '9.2.8' + pod 'NECommonKit', '9.2.8' + pod 'NECoreIMKit', '9.2.8' + pod 'NECoreKit', '9.2.8' # 如果需要查看UI部分源码请注释掉以上在线依赖,打开下面的本地依赖 From 7589781f422da8a6983d0520b8a06a2cb51244d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=96=84=E6=A0=8B?= Date: Mon, 19 Sep 2022 16:12:36 +0800 Subject: [PATCH 2/6] =?UTF-8?q?uikit=E5=B1=82=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NEChatUIKit/NEKitChatUI.podspec | 3 +- .../Assets/en.lproj/Localizable.strings | 179 +- .../Assets/zh-Hans.lproj/Localizable.strings | 35 +- .../Base/BaseView/ChatImageTextCell.swift | 1 - .../Classes/Base/BaseView/ChatTextCell.swift | 2 +- .../ChatBaseViewController.swift | 26 +- .../Chat/Controller/ChatViewController.swift | 1797 ++++++++--------- .../Controller/GroupChatViewController.swift | 2 +- .../Controller/P2PChatViewController.swift | 1 - .../Chat/Controller/ReadViewController.swift | 6 +- .../Controller/SelectUserViewController.swift | 2 +- .../UserSettingViewController.swift | 49 +- .../Chat/Emoji/InputEmoticonTabView.swift | 2 +- .../Classes/Chat/Helper/MessageUtils.swift | 6 +- .../Chat/Model/MessageContentModel.swift | 2 +- .../Chat/View/Cell/ChatVideoLeftCell.swift | 4 +- .../Chat/View/ChatBrokenNetworkView.swift | 2 +- .../Classes/Chat/View/ChatInputView.swift | 8 - .../Classes/Chat/View/ChatRecordView.swift | 2 +- .../View/ForwardAlertViewController.swift | 4 +- .../Classes/ChatRouter/ChatRouter.swift | 3 +- .../Classes/Common/NSBundleExtension.swift | 5 +- .../Extension/ChatStringExtension.swift | 6 +- NEContactUIKit/NEKitContactUI.podspec | 4 +- .../Assets/en.lproj/Localizable.strings | 38 +- .../Assets/zh-Hans.lproj/Localizable.strings | 29 +- .../BlackListViewController.swift | 2 +- .../Classes/Common/ContactRouter.swift | 6 +- .../TeamListViewController.swift | 2 +- .../ContactRemakNameViewController.swift | 6 +- .../ContactUserViewController.swift | 54 +- .../ValidationMessageViewController.swift | 6 +- .../Views/SystemNotificationCell.swift | 10 +- .../ContactsSelectedViewController.swift | 14 +- .../Views/ContactsViewController.swift | 6 +- .../Views/FindFriendViewController.swift | 4 +- .../NEKitConversationUI.podspec | 9 +- .../Assets/en.lproj/Localizable.strings | 31 +- .../Assets/zh-Hans.lproj/Localizable.strings | 22 + .../ConversationListViewController.swift | 5 +- .../ConversationSearchController.swift | 193 +- .../ConversationRouter.swift | 5 +- .../View/ConversationListCell.swift | 6 +- .../View/ConversationNavView.swift | 2 +- .../Classes/Util/NEMessageUtil.swift | 17 +- NEQChatUIKit/NEKitQChatUI.podspec | 3 +- .../Assets/en.lproj/Localizable.strings | 307 ++- .../Assets/zh-Hans.lproj/Localizable.strings | 42 +- .../Base/BaseView/QChatImageTextCell.swift | 1 - .../Classes/Base/BaseView/QChatTextCell.swift | 2 +- .../Channel/View/ChannelHeaderView.swift | 4 +- .../View/QChatPermissionSettingCell.swift | 2 +- .../Channel/View/QChatTextEditCell.swift | 3 +- .../ViewController/QChatAddMemberVC.swift | 2 +- .../QChatChannelMembersVC.swift | 2 +- .../QChatChannelViewController.swift | 11 +- .../QChatWhiteBlackListVC.swift | 7 +- .../Chat/Controller/QChatViewController.swift | 504 +++-- .../Chat/ViewModel/QChatViewModel.swift | 4 +- .../Extension/QChatStringExtension.swift | 8 +- .../CreateServerViewController.swift | 69 +- .../JoinOtherServiceController.swift | 143 +- .../Controller/MemberListViewController.swift | 133 +- .../MineCreateServerController.swift | 166 +- .../Controller/QChatHomeViewController.swift | 7 +- .../QChatHomePage/View/InviteMemberView.swift | 2 +- .../View/NEHomeChannelView.swift | 10 +- .../View/NESearchServerCell.swift | 6 +- .../Classes/Server/Model/IdGroupModel.swift | 11 - .../Server/Model/PermissionModel.swift | 2 +- .../QChatCreateGroupViewController.swift | 365 ++-- .../QChatEditMemberViewController.swift | 480 ++--- .../QChatIdGroupSortController.swift | 213 +- .../QChatIdGroupViewController.swift | 291 +-- .../QChatMemberManagerController.swift | 250 +-- .../QChatMemberSelectController.swift | 214 +- .../QChatPermissionViewController.swift | 358 ++-- .../QChatServerSettingViewController.swift | 402 ++-- NETeamUIKit/NEKitTeamUI.podspec | 3 +- .../Assets/en.lproj/Localizable.strings | 91 +- .../Assets/zh-Hans.lproj/Localizable.strings | 31 +- .../Setting/TeamAvatarViewController.swift | 181 +- .../TeamHistoryMessageController.swift | 133 +- .../Setting/TeamInfoViewController.swift | 99 +- .../Setting/TeamIntroduceViewController.swift | 83 +- .../Setting/TeamMembersController.swift | 100 +- .../Setting/TeamNameViewController.swift | 115 +- .../Setting/TeamSettingViewController.swift | 343 ++-- .../Classes/Setting/View/TeamMemberCell.swift | 2 +- .../Setting/ViewModel/TeamInfoViewModel.swift | 6 +- .../ViewModel/TeamSettingViewModel.swift | 4 +- .../NEKitTeamUI/Classes/TeamRouter.swift | 4 +- 92 files changed, 3993 insertions(+), 3844 deletions(-) diff --git a/NEChatUIKit/NEKitChatUI.podspec b/NEChatUIKit/NEKitChatUI.podspec index 4d74f8ff..704bc4ab 100644 --- a/NEChatUIKit/NEKitChatUI.podspec +++ b/NEChatUIKit/NEKitChatUI.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |spec| # spec.name = "NEKitChatUI" - spec.version = "9.2.7" + spec.version = "9.2.8" spec.summary = "Chat Module of IM." # This description is used to generate tags and improve search results. @@ -38,7 +38,6 @@ TODO: Add long description of the pod here. 'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES' } spec.user_target_xcconfig = { 'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES' } - spec.resource_bundles = { 'NEKitChatUI' => ['NEKitChatUI/Assets/*.png'] } diff --git a/NEChatUIKit/NEKitChatUI/Assets/en.lproj/Localizable.strings b/NEChatUIKit/NEKitChatUI/Assets/en.lproj/Localizable.strings index ca96635f..03544b99 100644 --- a/NEChatUIKit/NEKitChatUI/Assets/en.lproj/Localizable.strings +++ b/NEChatUIKit/NEKitChatUI/Assets/en.lproj/Localizable.strings @@ -4,98 +4,111 @@ // found in the LICENSE file. //MAKR:common -"ok"="ok"; -"send_to"="To "; - +"ok"="OK"; +"send_to"="send to"; //MAKR:message -"发来了一张图片"="Send an image to you"; -"发来了一段语音"="Send an audio to you"; -"发来了一段视频"="Send an video to you"; -"按住说话"="Hold down the talk"; -"send_after_let_go"="Release to send, press and hold to swipe to an empty area to cancel"; -"录音时间太短"="Recording time is too short"; -"没有麦克风权限"="No microphone access"; +"send_picture"="get a picture"; +"send_voice"="get a audio"; +"send_video"="get a video"; +"press_speak"="Hold to talk"; +"send_after_let_go"="Release to send, Slide to the blank area to cancel"; +"record_too_short"="Time is too short"; +"no_microphone_permission"="no micro permission"; +"choose"="select"; +"take_photo"="camera"; +"select_from_album"="album"; +"editing"="typing"; +"read"="read"; +"unread"="unread"; +"network_unavailable"="No internet, Please check your setting"; + //MARK: tips //MARK: team -"invite"="invite "; -"humans"="humans "; -"enter"="enter "; -"unknown_system_message"="Unknown System Message"; -"You"="You "; -"discussion_group"="discussion group"; +"invite"="Invite"; +"humans"="member"; +"enter"="enter"; +"unknown_system_message"="Unknown message"; +"You"="you"; +"discussion_group"="temp group"; "group"="group"; -"dissolve"="dissolved "; -"kick"="kick "; -"out"="out "; -"has_updated" = "has updated "; -"team_name" = " team name "; -"team_intro" = " introduce "; -"team_anouncement" = " anouncement "; -"team_avatar" = " avatar "; -"team_join_mode" = " join mode "; -"team_be_invited_mode" = " invited mode "; -"team_be_invited_permission" = " invited permission "; -"team_update_info_permission" = " permission of update info "; -"team_be_invited_author" = "Whether the invitee is required to agree permission"; -"team_update_client_custom"="permission of update client custom text"; -"team_custom_info" = "custom info "; -"not_mute" = " was unmuted "; -"mute" = " was muted "; -"team_has_been_removed" = " team has been removed "; -"join"=" join "; -"pass"=" pass "; -"leave"="leave "; -"transfer"=" transfer manager to "; -"added_manager"=" was as group manager "; -"removed_manager"=" group admin was revoked "; -"accept"=" has accepted group invitation from "; -"team_mute"="The group owner is set to mute"; -"team_all_mute"="The group was muted"; -"team_all_no_mute"="The group was unmuted"; -"session_set_top"="聊天置顶"; -"message_remind"="开启消息提醒"; -"pin_text"=" has pined this message"; +"dissolve"="dissolved"; +"kick"="将 "; +"out"="remove"; +"has_updated" = "updated"; + +"team_name" = "name"; +"team_intro" = "Info"; +"team_anouncement" = "Notice"; +"team_avatar" = "Avatar"; +"team_join_mode" = "join mode"; +"team_be_invited_mode" = "invite mode"; +"team_be_invited_permission" = "invite permission"; +"team_be_invited_author" = "是否需要被邀请者同意权限"; +"team_update_info_permission" = "update info permission"; +"team_update_client_custom"="update custom permission"; + +"team_custom_info" = "custom info"; +"not_mute" = "unmute"; +"mute" = "mute"; +"team_has_been_removed" = "This group was removed"; +"join"="joined"; +"pass"="Passed"; +"leave"="leave"; +"transfer"="Transferred the group leader identity to"; +"added_manager"="add to be Group administrator"; +"removed_manager"="Group administrator identity revoked"; +"accept"="Accept the group invitation from"; +"team_mute"=" Group owner set muting"; +"team_all_mute"="Mute all"; +"team_all_no_mute"="Unmute"; + +"pin_text"="pinned this message for both"; + +"session_set_top"="sticky to top"; +"message_remind"="open notification"; + +"open_soon"="Not open"; -"open_soon"="暂未开放"; //MARK: operation -"operation_copy"="Copy"; -"operation_replay"="Replay"; -"operation_forward"="Forward"; -"operation_pin"="Pin"; -"operation_select"="Select"; -"operation_collection"="Collection"; -"operation_delete"="Delete"; -"operation_recall"="Recall"; -"message_has_be_withdrawn" = "Message has be withdrawn"; -"message_reedit" = "Reedit message"; -"message_revoke_confim" = "Are you sure?"; -"message_delete_comfirm"="Are you sure?"; +"operation_copy"="copy"; +"operation_replay"="reply"; +"operation_forward"="forward"; +"operation_pin"="pin"; +"operation_cancel_pin"="unpin"; +"operation_select"="multi-select"; +"operation_collection"="favorite"; +"operation_delete"="delete"; +"operation_recall"="recall"; +"message_has_be_withdrawn" = "message recalled"; +"message_reedit" = "reedit"; +"message_revoke_confim" = "Wether to recall this message"; +"message_delete_comfirm"="Wether to delete this message"; -//MARK: toast -"copy_success"="Copy succeeded"; -"collection_success"="Collection Success"; -"no_permession"="No permession"; -"msg_reply"="Reply"; -"msg_image"="Image"; -"msg_audio"="Audio"; -"msg_video"="Video"; -"msg_file"="File"; -"msg_custom"="Custom"; -"msg_unknown"="Unknown"; -"message_read"="Read status"; -"message_all_unread"="No body has readed"; -"user_select"="Select Users"; -"user_select_all"="All"; +//MARK: toast +"copy_success"="Copy successfully"; +"collection_success"="Favorite"; +"no_permession"="No Permission"; +"msg_reply"="reply"; +"msg_image"="picture"; +"msg_audio"="audio"; +"msg_video"="video"; +"msg_file"="file"; +"msg_custom"="custom message"; +"msg_unknown"="Unknown message type"; +"message_read"="Reading Status"; +"message_all_unread"="all unread"; +"user_select"="notification"; +"user_select_all"="all"; -"contact_user"="个人"; -"team"="群组"; -"cancel"="取消"; -"send_to"="发送给"; +"contact_user"="contacts"; +"team"="group"; +"cancel"="cancel"; +"send_to"="send to"; +"send"="send"; +"confirm"="yes"; -"contact_user"="好友"; -"team"="群聊"; -"cancel"="取消"; -"send_to"="发送给"; +"mdhm"="MM.dd HH:mm"; +"ymdhm"="yyyy.MM.dd HH:mm"; diff --git a/NEChatUIKit/NEKitChatUI/Assets/zh-Hans.lproj/Localizable.strings b/NEChatUIKit/NEKitChatUI/Assets/zh-Hans.lproj/Localizable.strings index 8a836d25..933ad4ff 100644 --- a/NEChatUIKit/NEKitChatUI/Assets/zh-Hans.lproj/Localizable.strings +++ b/NEChatUIKit/NEKitChatUI/Assets/zh-Hans.lproj/Localizable.strings @@ -7,13 +7,27 @@ "ok"="确认"; "send_to"="发送给 "; //MAKR:message -"发来了一张图片"="发来了一张图"; -"发来了一段语音"="发来了一段语音"; -"发来了一段视频"="发来了一段视频"; -"按住说话"="按住说话"; +"send_picture"="发来了一张图片"; +"send_voice"="发来了一段语音"; +"send_video"="发来了一段视频"; +"press_speak"="按住说话"; "send_after_let_go"="松开发送,按住滑到空白区域取消"; -"录音时间太短"="录音时间太短"; -"没有麦克风权限"="没有麦克风权限"; +"record_too_short"="录音时间太短"; +"no_microphone_permission"="没有麦克风权限"; +"choose"="请选择"; +"take_photo"="拍照"; +"select_from_album"="从相册选择"; +"editing"="正在输入中..."; +"read"="已读(0)"; +"unread"="未读(0)"; +"network_unavailable"="当前网络不可用,请检查你当网络设置。"; + + + + + + + //MARK: tips //MARK: team @@ -54,12 +68,9 @@ "team_mute"="当前群主设置为禁言"; "team_all_mute"="群全体禁言"; "team_all_no_mute"="取消群全体禁言"; - "pin_text"="标记了这条信息,对话内容双方均可见"; - "session_set_top"="聊天置顶"; "message_remind"="开启消息提醒"; - "open_soon"="暂未开放"; //MARK: operation @@ -98,3 +109,9 @@ "team"="群聊"; "cancel"="取消"; "send_to"="发送给"; +"send"="发送"; +"confirm"="确定"; + + +"mdhm"="MM月dd日 HH:mm"; +"ymdhm"="yyyy年MM月dd日 HH:mm"; diff --git a/NEChatUIKit/NEKitChatUI/Classes/Base/BaseView/ChatImageTextCell.swift b/NEChatUIKit/NEKitChatUI/Classes/Base/BaseView/ChatImageTextCell.swift index 0fe2fe59..38f74bf2 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Base/BaseView/ChatImageTextCell.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Base/BaseView/ChatImageTextCell.swift @@ -67,7 +67,6 @@ class ChatImageTextCell: ChatStateCell { name.textColor = .white name.textAlignment = .center name.font = UIFont.systemFont(ofSize: 14.0) - name.text = "果冻" return name }() diff --git a/NEChatUIKit/NEKitChatUI/Classes/Base/BaseView/ChatTextCell.swift b/NEChatUIKit/NEKitChatUI/Classes/Base/BaseView/ChatTextCell.swift index 1c386288..d1446571 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Base/BaseView/ChatTextCell.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Base/BaseView/ChatTextCell.swift @@ -31,7 +31,7 @@ class ChatTextCell: ChatStateCell { titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor), titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), ]) - titleLabel.text = "删除" + titleLabel.text = localizable("operation_delete") detailLabel.font = UIFont.systemFont(ofSize: 16) detailLabel.translatesAutoresizingMaskIntoConstraints = false diff --git a/NEChatUIKit/NEKitChatUI/Classes/Base/BaseViewController/ChatBaseViewController.swift b/NEChatUIKit/NEKitChatUI/Classes/Base/BaseViewController/ChatBaseViewController.swift index c74ac360..9d98bc7c 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Base/BaseViewController/ChatBaseViewController.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Base/BaseViewController/ChatBaseViewController.swift @@ -6,18 +6,24 @@ import UIKit import NEKitCore -class ChatBaseViewController: NEBaseViewController { - override func viewDidLoad() { +@objc open class ChatBaseViewController: UIViewController { + override open func viewDidLoad() { super.viewDidLoad() + view.backgroundColor = .white + setupBackUI() } - /* - // MARK: - Navigation + private func setupBackUI() { + let image = UIImage.ne_imageNamed(name: "backArrow")?.withRenderingMode(.alwaysOriginal) + navigationItem.leftBarButtonItem = UIBarButtonItem( + image: image, + style: .plain, + target: self, + action: #selector(backEvent) + ) + } - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. - } - */ + @objc func backEvent() { + navigationController?.popViewController(animated: true) + } } diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/ChatViewController.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/ChatViewController.swift index 1e6e071e..59e3f02e 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/ChatViewController.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/ChatViewController.swift @@ -15,9 +15,10 @@ import NEKitCommonUI import WebKit @objcMembers -open class ChatViewController: NEBaseViewController, UINavigationControllerDelegate, +open class ChatViewController: ChatBaseViewController, UINavigationControllerDelegate, ChatInputViewDelegate, ChatViewModelDelegate, NIMMediaManagerDelegate, - MessageOperationViewDelegate, UIGestureRecognizerDelegate,UITableViewDataSource, UITableViewDelegate { + MessageOperationViewDelegate, UIGestureRecognizerDelegate, UITableViewDataSource, + UITableViewDelegate { private let tag = "ChatViewController" public var viewmodel: ChatViewModel private var inputViewTopConstraint: NSLayoutConstraint? @@ -178,7 +179,7 @@ open class ChatViewController: NEBaseViewController, UINavigationControllerDeleg } public func remoteUserEditing() { - title = "正在输入中..." + title = localizable("editing") trigerEndTimer() } @@ -216,650 +217,652 @@ open class ChatViewController: NEBaseViewController, UINavigationControllerDeleg deinit { print("will deinit") } - - - //MARK: objc 方法 - @objc func toSetting() { - if viewmodel.session.sessionType == .team { - Router.shared.use( - TeamSettingViewRouter, - parameters: ["nav": navigationController as Any, - "teamid": viewmodel.session.sessionId], - closure: nil - ) - } else if viewmodel.session.sessionType == .P2P { - let userSetting = UserSettingViewController() - userSetting.userId = viewmodel.session.sessionId - navigationController?.pushViewController(userSetting, animated: true) - } - } - //MARK: private 方法 - open func commonUI() { - title = viewmodel.session.sessionId - view.addSubview(tableView) - tableViewBottomConstraint = tableView.bottomAnchor.constraint( - equalTo: view.bottomAnchor, - constant: -100 - ) - tableViewBottomConstraint?.isActive = true - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint( - equalTo: view.topAnchor, - constant: kNavigationHeight + KStatusBarHeight - ), - tableView.leftAnchor.constraint(equalTo: view.leftAnchor), - tableView.rightAnchor.constraint(equalTo: view.rightAnchor), - ]) - tableView.register( - ChatTimeTableViewCell.self, - forCellReuseIdentifier: "\(ChatTimeTableViewCell.self)" - ) - tableView.register( - ChatBaseLeftCell.self, - forCellReuseIdentifier: "\(ChatBaseLeftCell.self)" - ) - tableView.register( - ChatBaseRightCell.self, - forCellReuseIdentifier: "\(ChatBaseRightCell.self)" - ) - tableView.register( - ChatTextRightCell.self, - forCellReuseIdentifier: "\(ChatTextRightCell.self)" - ) - tableView.register( - ChatTextLeftCell.self, - forCellReuseIdentifier: "\(ChatTextLeftCell.self)" - ) - tableView.register( - ChatAudioLeftCell.self, - forCellReuseIdentifier: "\(ChatAudioLeftCell.self)" - ) - tableView.register( - ChatAudioRightCell.self, - forCellReuseIdentifier: "\(ChatAudioRightCell.self)" - ) - tableView.register( - ChatImageLeftCell.self, - forCellReuseIdentifier: "\(ChatImageLeftCell.self)" - ) - tableView.register( - ChatImageRightCell.self, - forCellReuseIdentifier: "\(ChatImageRightCell.self)" - ) + // MARK: objc 方法 - tableView.register( - ChatRevokeLeftCell.self, - forCellReuseIdentifier: "\(ChatRevokeLeftCell.self)" - ) - tableView.register( - ChatRevokeRightCell.self, - forCellReuseIdentifier: "\(ChatRevokeRightCell.self)" + func toSetting() { + if viewmodel.session.sessionType == .team { + Router.shared.use( + TeamSettingViewRouter, + parameters: ["nav": navigationController as Any, + "teamid": viewmodel.session.sessionId], + closure: nil ) + } else if viewmodel.session.sessionType == .P2P { + let userSetting = UserSettingViewController() + userSetting.userId = viewmodel.session.sessionId + navigationController?.pushViewController(userSetting, animated: true) + } + } - tableView.register( - ChatVideoLeftCell.self, - forCellReuseIdentifier: "\(ChatVideoLeftCell.self)" - ) - tableView.register( - ChatVideoRightCell.self, - forCellReuseIdentifier: "\(ChatVideoRightCell.self)" - ) + // MARK: private 方法 - tableView.register( - ChatReplyRightCell.self, - forCellReuseIdentifier: "\(ChatReplyRightCell.self)" - ) - tableView.register( - ChatReplyLeftCell.self, - forCellReuseIdentifier: "\(ChatReplyLeftCell.self)" - ) + open func commonUI() { + title = viewmodel.session.sessionId + view.addSubview(tableView) + tableViewBottomConstraint = tableView.bottomAnchor.constraint( + equalTo: view.bottomAnchor, + constant: -100 + ) + tableViewBottomConstraint?.isActive = true + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint( + equalTo: view.topAnchor, + constant: kNavigationHeight + KStatusBarHeight + ), + tableView.leftAnchor.constraint(equalTo: view.leftAnchor), + tableView.rightAnchor.constraint(equalTo: view.rightAnchor), + ]) + tableView.register( + ChatTimeTableViewCell.self, + forCellReuseIdentifier: "\(ChatTimeTableViewCell.self)" + ) - menuView.translatesAutoresizingMaskIntoConstraints = false - menuView.delegate = self - view.addSubview(menuView) + tableView.register( + ChatBaseLeftCell.self, + forCellReuseIdentifier: "\(ChatBaseLeftCell.self)" + ) + tableView.register( + ChatBaseRightCell.self, + forCellReuseIdentifier: "\(ChatBaseRightCell.self)" + ) + tableView.register( + ChatTextRightCell.self, + forCellReuseIdentifier: "\(ChatTextRightCell.self)" + ) + tableView.register( + ChatTextLeftCell.self, + forCellReuseIdentifier: "\(ChatTextLeftCell.self)" + ) + tableView.register( + ChatAudioLeftCell.self, + forCellReuseIdentifier: "\(ChatAudioLeftCell.self)" + ) + tableView.register( + ChatAudioRightCell.self, + forCellReuseIdentifier: "\(ChatAudioRightCell.self)" + ) + tableView.register( + ChatImageLeftCell.self, + forCellReuseIdentifier: "\(ChatImageLeftCell.self)" + ) + tableView.register( + ChatImageRightCell.self, + forCellReuseIdentifier: "\(ChatImageRightCell.self)" + ) - inputViewTopConstraint = menuView.topAnchor.constraint( - equalTo: view.bottomAnchor, - constant: -100 - ) - NSLayoutConstraint.activate([ - menuView.leftAnchor.constraint(equalTo: view.leftAnchor), - menuView.rightAnchor.constraint(equalTo: view.rightAnchor), - menuView.heightAnchor.constraint(equalToConstant: 304), - ]) - inputViewTopConstraint?.isActive = true + tableView.register( + ChatRevokeLeftCell.self, + forCellReuseIdentifier: "\(ChatRevokeLeftCell.self)" + ) + tableView.register( + ChatRevokeRightCell.self, + forCellReuseIdentifier: "\(ChatRevokeRightCell.self)" + ) - weak var weakSelf = self - NEChatDetectNetworkTool.shareInstance.netWorkReachability { status in - if status == .notReachable, let networkView = weakSelf?.brokenNetworkView { - weakSelf?.view.addSubview(networkView) - } else { - weakSelf?.brokenNetworkView.removeFromSuperview() - } + tableView.register( + ChatVideoLeftCell.self, + forCellReuseIdentifier: "\(ChatVideoLeftCell.self)" + ) + tableView.register( + ChatVideoRightCell.self, + forCellReuseIdentifier: "\(ChatVideoRightCell.self)" + ) + + tableView.register( + ChatReplyRightCell.self, + forCellReuseIdentifier: "\(ChatReplyRightCell.self)" + ) + tableView.register( + ChatReplyLeftCell.self, + forCellReuseIdentifier: "\(ChatReplyLeftCell.self)" + ) + + menuView.translatesAutoresizingMaskIntoConstraints = false + menuView.delegate = self + view.addSubview(menuView) + + inputViewTopConstraint = menuView.topAnchor.constraint( + equalTo: view.bottomAnchor, + constant: -100 + ) + NSLayoutConstraint.activate([ + menuView.leftAnchor.constraint(equalTo: view.leftAnchor), + menuView.rightAnchor.constraint(equalTo: view.rightAnchor), + menuView.heightAnchor.constraint(equalToConstant: 304), + ]) + inputViewTopConstraint?.isActive = true + + weak var weakSelf = self + NEChatDetectNetworkTool.shareInstance.netWorkReachability { status in + if status == .notReachable, let networkView = weakSelf?.brokenNetworkView { + weakSelf?.view.addSubview(networkView) + } else { + weakSelf?.brokenNetworkView.removeFromSuperview() } - addRightAction(UIImage.ne_imageNamed(name: "three_point"), #selector(toSetting), self) } + addRightAction(UIImage.ne_imageNamed(name: "three_point"), #selector(toSetting), self) + } - func loadData() { - // title - getSessionInfo(session: viewmodel.session) - weak var weakSelf = self - - viewmodel.queryRoamMsgHasMoreTime_v2 { error, historyEnd, newEnd, models, index in - if let ms = models, ms.count > 0 { - if let messages = weakSelf?.viewmodel.messages { - for index in 0 ..< messages.count { - let message = messages[index] - if message.message?.messageId == weakSelf?.viewmodel.anchor?.messageId { - print("messages real index : ", index) - print("messages text : ", message.message?.text as Any) - } + func loadData() { + // title + getSessionInfo(session: viewmodel.session) + weak var weakSelf = self + + viewmodel.queryRoamMsgHasMoreTime_v2 { error, historyEnd, newEnd, models, index in + if let ms = models, ms.count > 0 { + if let messages = weakSelf?.viewmodel.messages { + for index in 0 ..< messages.count { + let message = messages[index] + if message.message?.messageId == weakSelf?.viewmodel.anchor?.messageId { + print("messages real index : ", index) + print("messages text : ", message.message?.text as Any) } } - weakSelf?.tableView.reloadData() - if weakSelf?.viewmodel.isHistoryChat == true { - let indexPath = IndexPath(row: index, section: 0) - print("queryRoamMsgHasMoreTime_v2 index : ", index) - weakSelf?.tableView.scrollToRow(at: indexPath, at: .none, animated: false) - if newEnd <= 0 { - weakSelf?.addBottomLoadMore() - } - } else { - if let tempArray = weakSelf?.viewmodel.messages, tempArray.count > 0 { - weakSelf?.tableView.reloadData() - weakSelf?.tableView.scrollToRow( - at: IndexPath(row: tempArray.count - 1, section: 0), - at: .bottom, - animated: false - ) - } + } + weakSelf?.tableView.reloadData() + if weakSelf?.viewmodel.isHistoryChat == true { + let indexPath = IndexPath(row: index, section: 0) + print("queryRoamMsgHasMoreTime_v2 index : ", index) + weakSelf?.tableView.scrollToRow(at: indexPath, at: .none, animated: false) + if newEnd <= 0 { + weakSelf?.addBottomLoadMore() + } + } else { + if let tempArray = weakSelf?.viewmodel.messages, tempArray.count > 0 { + weakSelf?.tableView.reloadData() + weakSelf?.tableView.scrollToRow( + at: IndexPath(row: tempArray.count - 1, section: 0), + at: .bottom, + animated: false + ) } - - } else if let err = error { - weakSelf?.showToast(err.localizedDescription) } - } - // if viewmodel.isHistoryChat == false { - // viewmodel.getMessageHistory({[weak self] error,isEmpty,messages in - // if let err = error { - // NELog.errorLog(self?.tag ?? "ChatViewController", desc: "❌getMessageHistory error, error:\(err)") - // }else { - // if let tempArray = weakSelf?.viewmodel.messages,tempArray.count > 0 { - // weakSelf?.tableView.reloadData() - // weakSelf?.tableView.scrollToRow(at: IndexPath(row: tempArray.count - 1, section: 0), at: .bottom, animated: false) - // } - // } - // }) - // }else { - // print("queryRoamMsgHasMoreTime") - // viewmodel.queryRoamMsgHasMoreTime_v2 { error, historyEnd, newEnd, models, index in - // if let ms = models, ms.count > 0 { - // if let messages = weakSelf?.viewmodel.messages { - // for index in 0.. 0 { + // weakSelf?.tableView.reloadData() + // weakSelf?.tableView.scrollToRow(at: IndexPath(row: tempArray.count - 1, section: 0), at: .bottom, animated: false) + // } + // } + // }) + // }else { + // print("queryRoamMsgHasMoreTime") + // viewmodel.queryRoamMsgHasMoreTime_v2 { error, historyEnd, newEnd, models, index in + // if let ms = models, ms.count > 0 { + // if let messages = weakSelf?.viewmodel.messages { + // for index in 0.. 0 { - weakSelf?.tableView.scrollToRow( - at: IndexPath(row: count, section: 0), - at: .top, - animated: false - ) - } - weakSelf?.tableView.mj_header?.endRefreshing() - } + func loadMoreData() { + weak var weakSelf = self + viewmodel.dropDownRemoteRefresh { error, count, messages in + print("dropDownRemoteRefresh messages count ", messages?.count as Any) - // viewmodel.getMoreMessageHistory { error, isEmpty, messageFrames in - // weakSelf?.tableView.reloadData() - // weakSelf?.tableView.mj_header?.endRefreshing() - // } + weakSelf?.tableView.reloadData() + if count > 0 { + weakSelf?.tableView.scrollToRow( + at: IndexPath(row: count, section: 0), + at: .top, + animated: false + ) + } + weakSelf?.tableView.mj_header?.endRefreshing() } - @objc func loadFartherToNowData() {} + // viewmodel.getMoreMessageHistory { error, isEmpty, messageFrames in + // weakSelf?.tableView.reloadData() + // weakSelf?.tableView.mj_header?.endRefreshing() + // } + } - @objc func loadCloserToNowData() { - weak var weakSelf = self - viewmodel.pullRemoteRefresh { error, end, datas in - if end > 0 { - weakSelf?.removeBottomLoadMore() - } else { - weakSelf?.tableView.mj_footer?.endRefreshing() - weakSelf?.tableView.reloadData() - } + func loadFartherToNowData() {} + + func loadCloserToNowData() { + weak var weakSelf = self + viewmodel.pullRemoteRefresh { error, end, datas in + if end > 0 { + weakSelf?.removeBottomLoadMore() + } else { + weakSelf?.tableView.mj_footer?.endRefreshing() + weakSelf?.tableView.reloadData() } } + } - func addObseve() { - NotificationCenter.default.addObserver(self, - selector: #selector(keyBoardWillShow(_:)), - name: UIResponder.keyboardWillShowNotification, - object: nil) - - NotificationCenter.default.addObserver(self, - selector: #selector(keyBoardWillHide(_:)), - name: UIResponder.keyboardWillHideNotification, - object: nil) - // let tap = UITapGestureRecognizer(target: self, action: #selector(viewTap)) - // tap.delegate = self - // self.view.addGestureRecognizer(tap) - } + func addObseve() { + NotificationCenter.default.addObserver(self, + selector: #selector(keyBoardWillShow(_:)), + name: UIResponder.keyboardWillShowNotification, + object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(keyBoardWillHide(_:)), + name: UIResponder.keyboardWillHideNotification, + object: nil) + // let tap = UITapGestureRecognizer(target: self, action: #selector(viewTap)) + // tap.delegate = self + // self.view.addGestureRecognizer(tap) + } - func addBottomLoadMore() { - tableView.mj_footer = MJRefreshBackNormalFooter( - refreshingTarget: self, - refreshingAction: #selector(loadCloserToNowData) - ) - } + func addBottomLoadMore() { + tableView.mj_footer = MJRefreshBackNormalFooter( + refreshingTarget: self, + refreshingAction: #selector(loadCloserToNowData) + ) + } - func removeBottomLoadMore() { - tableView.mj_footer?.endRefreshingWithNoMoreData() - tableView.mj_footer = nil - viewmodel.isHistoryChat = false // 转为普通聊天页面 - } + func removeBottomLoadMore() { + tableView.mj_footer?.endRefreshingWithNoMoreData() + tableView.mj_footer = nil + viewmodel.isHistoryChat = false // 转为普通聊天页面 + } // MARK: 键盘通知相关操作 - @objc func keyBoardWillShow(_ notification: Notification) { - if menuView.currentType != .text { - return - } - let keyboardRect = (notification - .userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue - layoutInputView(offset: keyboardRect.size.height) - - UIView.animate(withDuration: 0.25, animations: { - self.view.layoutIfNeeded() - }) - scrollTableViewToBottom() + func keyBoardWillShow(_ notification: Notification) { + if menuView.currentType != .text { + return } + let keyboardRect = (notification + .userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue + layoutInputView(offset: keyboardRect.size.height) - @objc func keyBoardWillHide(_ notification: Notification) { - if menuView.currentType != .text { - return - } - if operationView?.superview != nil { - operationView?.removeFromSuperview() - } - layoutInputView(offset: 0) - scrollTableViewToBottom() + UIView.animate(withDuration: 0.25, animations: { + self.view.layoutIfNeeded() + }) + scrollTableViewToBottom() + } + + func keyBoardWillHide(_ notification: Notification) { + if menuView.currentType != .text { + return } + // 解决点击operation点击无效问题 +// if operationView?.superview != nil { +// operationView?.removeFromSuperview() +// } + layoutInputView(offset: 0) + scrollTableViewToBottom() + } - private func scrollTableViewToBottom() { - print("self.viewmodel.messages.count\(viewmodel.messages.count)") - print("self.tableView.numberOfRows(inSection: 0)\(tableView.numberOfRows(inSection: 0))") - if viewmodel.messages.count > 0 { - let indexPath = IndexPath(row: viewmodel.messages.count - 1, section: 0) - tableView.scrollToRow(at: indexPath, at: .bottom, animated: true) - } + private func scrollTableViewToBottom() { + print("self.viewmodel.messages.count\(viewmodel.messages.count)") + print("self.tableView.numberOfRows(inSection: 0)\(tableView.numberOfRows(inSection: 0))") + if viewmodel.messages.count > 0 { + let indexPath = IndexPath(row: viewmodel.messages.count - 1, section: 0) + tableView.scrollToRow(at: indexPath, at: .bottom, animated: true) } + } // offset:value which from self.view.bottom to inputView.bottom - private func layoutInputView(offset: CGFloat) { - inputViewTopConstraint?.constant = -100 - offset - tableViewBottomConstraint?.constant = -100 - offset - UIView.animate(withDuration: 0.25, animations: { - self.view.layoutIfNeeded() - }) - } + private func layoutInputView(offset: CGFloat) { + inputViewTopConstraint?.constant = -100 - offset + tableViewBottomConstraint?.constant = -100 - offset + UIView.animate(withDuration: 0.25, animations: { + self.view.layoutIfNeeded() + }) + } // MARK: ChatInputViewDelegate - public func sendText(text: String?) { - guard let content = text, content.count > 0 else { - showToast(localizable("text_is_nil")) - return - } - if viewmodel.isReplying, let msg = viewmodel.operationModel?.message { - viewmodel - .replyMessage(MessageUtils.textMessage(text: content), msg) { [weak self] error in - if error != nil { - self?.view.makeToast(error?.localizedDescription) - } else { - self?.viewmodel.isReplying = false - self?.replyView.removeFromSuperview() - } - } - - } else { - viewmodel.sendTextMessage(text: content) { [weak self] error in + public func sendText(text: String?) { + guard let content = text, content.count > 0 else { + return + } + if viewmodel.isReplying, let msg = viewmodel.operationModel?.message { + viewmodel + .replyMessage(MessageUtils.textMessage(text: content), msg) { [weak self] error in if error != nil { self?.view.makeToast(error?.localizedDescription) + } else { + self?.viewmodel.isReplying = false + self?.replyView.removeFromSuperview() } } - } - } - public func textChanged(text: String) -> Bool { - if text == "@" { - // 做p2p类型判断 - if viewmodel.session.sessionType == .P2P { - return true - } else { - showUserSelectVC(text: text) - return false + } else { + viewmodel.sendTextMessage(text: content) { [weak self] error in + if error != nil { + self?.view.makeToast(error?.localizedDescription) } - - } else { - return true } } + } - public func textDelete(range: NSRange, text: String) -> Bool { - var index = -1 - var removeRange: NSRange? - for (i, r) in atUsers.enumerated() { - let rightIndex = r.location + r.length - 1 - if rightIndex == range.location { - index = i - removeRange = r - break - } - } - if index >= 0 { - atUsers.remove(at: index) - if let text = menuView.textField.text { - menuView.textField.text = text - .substring(to: text.index(text.startIndex, offsetBy: removeRange!.location)) - } + public func textChanged(text: String) -> Bool { + if text == "@" { + // 做p2p类型判断 + if viewmodel.session.sessionType == .P2P { + return true + } else { + showUserSelectVC(text: text) return false } + + } else { return true } + } - public func textFieldDidChange(_ textField: UITextView) { - if let text = textField.text { - if text.count > 0 { - viewmodel.sendInputTypingState() - } else { - viewmodel.sendInputTypingEndState() - } + public func textDelete(range: NSRange, text: String) -> Bool { + var index = -1 + var removeRange: NSRange? + for (i, r) in atUsers.enumerated() { + let rightIndex = r.location + r.length - 1 + if rightIndex == range.location { + index = i + removeRange = r + break } } - - public func textFieldDidEndEditing(_ textField: UITextView) { - viewmodel.sendInputTypingEndState() - } - - public func textFieldDidBeginEditing(_ textField: UITextView) { - if let count = textField.text?.count, count > 0 { - viewmodel.sendInputTypingState() + if index >= 0 { + atUsers.remove(at: index) + if let text = menuView.textField.text { + menuView.textField.text = text + .substring(to: text.index(text.startIndex, offsetBy: removeRange!.location)) } + return false } + return true + } - public func willSelectItem(button: UIButton, index: Int) { - if index == 0 { - layoutInputView(offset: 204) - scrollTableViewToBottom() - } else if index == 1 { - layoutInputView(offset: 204) - scrollTableViewToBottom() - } else if index == 2 { - // showMenue(sourceView: view) - // showBottomAlert(self, false) - goPhotoAlbumWithVideo(self) - } else if index == 3 { - showBottomVideoAction(self, false) + public func textFieldDidChange(_ textField: UITextView) { + if let text = textField.text { + if text.count > 0 { + viewmodel.sendInputTypingState() } else { - showToast(localizable("open_soon")) + viewmodel.sendInputTypingEndState() } } + } - func showMenue(sourceView: UIView) { - let alert = UIAlertController( - title: localizable("请选择"), - message: nil, - preferredStyle: .actionSheet - ) - alert.modalPresentationStyle = .popover - let camera = UIAlertAction(title: localizable("拍照"), style: .default) { action in - self.takePhoto() - } - let photo = UIAlertAction(title: localizable("从相册选择"), style: .default) { action in - self.willSelectImage() - } + public func textFieldDidEndEditing(_ textField: UITextView) { + viewmodel.sendInputTypingEndState() + } - let cancel = UIAlertAction(title: "取消", style: .cancel) { action in - } + public func textFieldDidBeginEditing(_ textField: UITextView) { + if let count = textField.text?.count, count > 0 { + viewmodel.sendInputTypingState() + } + } - alert.addAction(camera) - alert.addAction(photo) - alert.addAction(cancel) - let popover = alert.popoverPresentationController - if popover != nil { - popover?.sourceView = sourceView - popover?.permittedArrowDirections = .any - } - present(alert, animated: true, completion: nil) + public func willSelectItem(button: UIButton, index: Int) { + if index == 0 { + layoutInputView(offset: 204) + scrollTableViewToBottom() + } else if index == 1 { + layoutInputView(offset: 204) + scrollTableViewToBottom() + } else if index == 2 { + // showMenue(sourceView: view) + // showBottomAlert(self, false) + goPhotoAlbumWithVideo(self) + } else if index == 3 { + showBottomVideoAction(self, false) + } else { + showToast(localizable("open_soon")) } + } - func willSelectImage() { - let imagePickerVC = UIImagePickerController() - imagePickerVC.delegate = self - imagePickerVC.allowsEditing = false - imagePickerVC.sourceType = .photoLibrary - present(imagePickerVC, animated: true) {} + func showMenue(sourceView: UIView) { + let alert = UIAlertController( + title: localizable("choose"), + message: nil, + preferredStyle: .actionSheet + ) + alert.modalPresentationStyle = .popover + let camera = UIAlertAction(title: localizable("take_photo"), style: .default) { action in + self.takePhoto() + } + let photo = UIAlertAction(title: localizable("select_from_album"), style: .default) { action in + self.willSelectImage() } - func takePhoto() { - let imagePickerVC = UIImagePickerController() - imagePickerVC.delegate = self - imagePickerVC.allowsEditing = false - imagePickerVC.sourceType = .camera - present(imagePickerVC, animated: true) {} + let cancel = UIAlertAction(title: localizable("cancel"), style: .cancel) { action in } + alert.addAction(camera) + alert.addAction(photo) + alert.addAction(cancel) + let popover = alert.popoverPresentationController + if popover != nil { + popover?.sourceView = sourceView + popover?.permittedArrowDirections = .any + } + present(alert, animated: true, completion: nil) + } + + func willSelectImage() { + let imagePickerVC = UIImagePickerController() + imagePickerVC.delegate = self + imagePickerVC.allowsEditing = false + imagePickerVC.sourceType = .photoLibrary + present(imagePickerVC, animated: true) {} + } + + func takePhoto() { + let imagePickerVC = UIImagePickerController() + imagePickerVC.delegate = self + imagePickerVC.allowsEditing = false + imagePickerVC.sourceType = .camera + present(imagePickerVC, animated: true) {} + } + // MARK: UIImagePickerControllerDelegate - public func imagePickerController(_ picker: UIImagePickerController, - didFinishPickingMediaWithInfo info: [UIImagePickerController - .InfoKey: Any]) { - // send message + public func imagePickerController(_ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController + .InfoKey: Any]) { + // send message - picker.dismiss(animated: true, completion: nil) + picker.dismiss(animated: true, completion: nil) - if let url = info[.mediaURL] as? URL { - // video - print("image picker video : url", url) - // NELog.infoLog("send video message", desc: error?.localizedDescription ?? "no error") - weak var weakSelf = self - viewmodel.sendVideoMessage(url: url) { error in + if let url = info[.mediaURL] as? URL { + // video + print("image picker video : url", url) + // NELog.infoLog("send video message", desc: error?.localizedDescription ?? "no error") + weak var weakSelf = self + viewmodel.sendVideoMessage(url: url) { error in - if let err = error { - NELog.errorLog("send video message", desc: err.localizedDescription) - weakSelf?.showToast(err.localizedDescription) - } + if let err = error { + NELog.errorLog("send video message", desc: err.localizedDescription) + weakSelf?.showToast(err.localizedDescription) } - return } + return + } - guard let image = info[.originalImage] as? UIImage else { - showToast(localizable("image_is_nil")) - return - } - viewmodel.sendImageMessage(image: image) { [weak self] error in - NELog.infoLog("send image message", desc: error?.localizedDescription ?? "no error") - if error != nil { - self?.view.makeToast(error?.localizedDescription) - } else {} - } + guard let image = info[.originalImage] as? UIImage else { + showToast(localizable("image_is_nil")) + return + } + viewmodel.sendImageMessage(image: image) { [weak self] error in + NELog.infoLog("send image message", desc: error?.localizedDescription ?? "no error") + if error != nil { + self?.view.makeToast(error?.localizedDescription) + } else {} } + } // MARK: ChatViewModelDelegate - public func onRecvMessages(_ messages: [NIMMessage]) { - insertRows() - viewmodel.markRead(messages: messages) { error in - print("mark read \(error?.localizedDescription)") - } + public func onRecvMessages(_ messages: [NIMMessage]) { + insertRows() + viewmodel.markRead(messages: messages) { error in + print("mark read \(error?.localizedDescription)") } + } - public func willSend(_ message: NIMMessage) { - insertRows() - } + public func willSend(_ message: NIMMessage) { + insertRows() + } - public func send(_ message: NIMMessage, progress: Float) {} + public func send(_ message: NIMMessage, progress: Float) {} - public func send(_ message: NIMMessage, didCompleteWithError error: Error?) { - if indexPathsWithMessags([message]).count > 0 { - tableViewReloadIndexs(indexPathsWithMessags([message])) - } + public func send(_ message: NIMMessage, didCompleteWithError error: Error?) { + if indexPathsWithMessags([message]).count > 0 { + tableViewReloadIndexs(indexPathsWithMessags([message])) } + } - private func indexPathsWithMessags(_ messages: [NIMMessage]) -> [IndexPath] { - var indexPaths = [IndexPath]() - for messageModel in messages { - for (i, model) in viewmodel.messages.enumerated() { - if model.message?.messageId == messageModel.messageId { - indexPaths.append(IndexPath(row: i, section: 0)) - } + private func indexPathsWithMessags(_ messages: [NIMMessage]) -> [IndexPath] { + var indexPaths = [IndexPath]() + for messageModel in messages { + for (i, model) in viewmodel.messages.enumerated() { + if model.message?.messageId == messageModel.messageId { + indexPaths.append(IndexPath(row: i, section: 0)) } } - return indexPaths - } - - public func onDeleteMessage(_ message: NIMMessage, atIndexs: [IndexPath]) { - if atIndexs.isEmpty { - return - } - // self.tableView.reloadData() - tableViewDeleteIndexs(atIndexs) } + return indexPaths + } - public func updateDownloadProgress(_ message: NIMMessage, atIndex: IndexPath, progress: Float) { - tableViewUpdateDownload(atIndex) + public func onDeleteMessage(_ message: NIMMessage, atIndexs: [IndexPath]) { + if atIndexs.isEmpty { + return } + // self.tableView.reloadData() + tableViewDeleteIndexs(atIndexs) + } - public func onRevokeMessage(_ message: NIMMessage, atIndexs: [IndexPath]) { - if atIndexs.isEmpty { - return - } - tableViewReloadIndexs(atIndexs) - } + public func updateDownloadProgress(_ message: NIMMessage, atIndex: IndexPath, progress: Float) { + tableViewUpdateDownload(atIndex) + } - public func onAddMessagePin(_ message: NIMMessage, atIndexs: [IndexPath]) { - tableViewReloadIndexs(atIndexs) + public func onRevokeMessage(_ message: NIMMessage, atIndexs: [IndexPath]) { + if atIndexs.isEmpty { + return } + tableViewReloadIndexs(atIndexs) + } - public func onRemoveMessagePin(_ message: NIMMessage, atIndexs: [IndexPath]) { - tableViewReloadIndexs(atIndexs) - } + public func onAddMessagePin(_ message: NIMMessage, atIndexs: [IndexPath]) { + tableViewReloadIndexs(atIndexs) + } - public func tableViewDeleteIndexs(_ indexs: [IndexPath]) { - tableView.beginUpdates() - tableView.deleteRows(at: indexs, with: .none) - tableView.endUpdates() - } + public func onRemoveMessagePin(_ message: NIMMessage, atIndexs: [IndexPath]) { + tableViewReloadIndexs(atIndexs) + } - public func tableViewReloadIndexs(_ indexs: [IndexPath]) { - tableView.beginUpdates() - tableView.reloadRows(at: indexs, with: .none) - tableView.endUpdates() - } + public func tableViewDeleteIndexs(_ indexs: [IndexPath]) { + tableView.beginUpdates() + tableView.deleteRows(at: indexs, with: .none) + tableView.endUpdates() + } - public func didReadedMessageIndexs() { - if let indexPaths = tableView.indexPathsForVisibleRows, indexPaths.count > 0 { - tableView.beginUpdates() - // self.tableView.reloadRows(at: indexs, with: .none) - tableView.reloadRows(at: indexPaths, with: .none) - tableView.endUpdates() - } - } + public func tableViewReloadIndexs(_ indexs: [IndexPath]) { + tableView.beginUpdates() + tableView.reloadRows(at: indexs, with: .none) + tableView.endUpdates() + } - public func tableViewUpdateDownload(_ index: IndexPath) { + public func didReadedMessageIndexs() { + if let indexPaths = tableView.indexPathsForVisibleRows, indexPaths.count > 0 { tableView.beginUpdates() - tableView.reloadRows(at: [index], with: .none) + // self.tableView.reloadRows(at: indexs, with: .none) + tableView.reloadRows(at: indexPaths, with: .none) tableView.endUpdates() } + } - // record audio - public func startRecord() { - let dur = 60.0 - if NEAuthManager.hasAudioAuthoriztion() { - NIMSDK.shared().mediaManager.record(forDuration: dur) - } else { - NEAuthManager.requestAudioAuthorization { granted in - if granted { - } else { - DispatchQueue.main.async { - self.showToast(localizable("没有麦克风权限")) - } + public func tableViewUpdateDownload(_ index: IndexPath) { + tableView.beginUpdates() + tableView.reloadRows(at: [index], with: .none) + tableView.endUpdates() + } + + // record audio + public func startRecord() { + let dur = 60.0 + if NEAuthManager.hasAudioAuthoriztion() { + NIMSDK.shared().mediaManager.record(forDuration: dur) + } else { + NEAuthManager.requestAudioAuthorization { granted in + if granted { + } else { + DispatchQueue.main.async { + self.showToast(localizable("no_microphone_permission")) } } } } + } - public func moveOutView() {} + public func moveOutView() {} - public func moveInView() {} + public func moveInView() {} - public func endRecord(insideView: Bool) { - print("[record] stop:\(insideView)") - if insideView { - // send - NIMSDK.shared().mediaManager.stopRecord() - } else { - // cancel - NIMSDK.shared().mediaManager.cancelRecord() - } + public func endRecord(insideView: Bool) { + print("[record] stop:\(insideView)") + if insideView { + // send + NIMSDK.shared().mediaManager.stopRecord() + } else { + // cancel + NIMSDK.shared().mediaManager.cancelRecord() } + } - @objc func viewTap(tap: UITapGestureRecognizer) { - operationView?.removeFromSuperview() - if menuView.textField.isFirstResponder { - menuView.textField.resignFirstResponder() - } else { - layoutInputView(offset: 0) - } + func viewTap(tap: UITapGestureRecognizer) { + operationView?.removeFromSuperview() + if menuView.textField.isFirstResponder { + menuView.textField.resignFirstResponder() + } else { + layoutInputView(offset: 0) } + } - // MARK: audio play + // MARK: audio play - private func startPlay(cell: ChatAudioCell?, audio: NIMAudioObject) { - if cell?.isPlaying == true { - stopPlay() - } else { - stopPlay() - playingCell = cell - playingCell?.startAnimation() - if let url = audio.path { - NIMSDK.shared().mediaManager.switch(.speaker) - NIMSDK.shared().mediaManager.play(url) - } + private func startPlay(cell: ChatAudioCell?, audio: NIMAudioObject) { + if cell?.isPlaying == true { + stopPlay() + } else { + stopPlay() + playingCell = cell + playingCell?.startAnimation() + if let url = audio.path { + NIMSDK.shared().mediaManager.switch(.speaker) + NIMSDK.shared().mediaManager.play(url) } } + } - private func stopPlay() { - if NIMSDK.shared().mediaManager.isPlaying() { - playingCell?.startAnimation() - NIMSDK.shared().mediaManager.stopPlay() - } + private func stopPlay() { + if NIMSDK.shared().mediaManager.isPlaying() { + playingCell?.startAnimation() + NIMSDK.shared().mediaManager.stopPlay() } + } // private func startPlay() { // if NIMSDK.shared().mediaManager.isPlaying() { @@ -871,470 +874,468 @@ open class ChatViewController: NEBaseViewController, UINavigationControllerDeleg // MARK: NIMMediaManagerDelegate // play - public func playAudio(_ filePath: String, didBeganWithError error: Error?) { - print(#function + "\(error)") - if let e = error { - showToast(e.localizedDescription) - // stop - playingCell?.stopAnimation() - } - } - - public func playAudio(_ filePath: String, didCompletedWithError error: Error?) { - print(#function + "\(error)") - if let e = error { - showToast(e.localizedDescription) - } + public func playAudio(_ filePath: String, didBeganWithError error: Error?) { + print(#function + "\(error)") + if let e = error { + showToast(e.localizedDescription) // stop playingCell?.stopAnimation() } + } - public func stopPlayAudio(_ filePath: String, didCompletedWithError error: Error?) { - print(#function + "\(error)") - if let e = error { - showToast(e.localizedDescription) - } - playingCell?.stopAnimation() + public func playAudio(_ filePath: String, didCompletedWithError error: Error?) { + print(#function + "\(error)") + if let e = error { + showToast(e.localizedDescription) } + // stop + playingCell?.stopAnimation() + } - public func playAudio(_ filePath: String, progress value: Float) {} - - public func playAudioInterruptionEnd() { - print(#function) + public func stopPlayAudio(_ filePath: String, didCompletedWithError error: Error?) { + print(#function + "\(error)") + if let e = error { + showToast(e.localizedDescription) } + playingCell?.stopAnimation() + } - public func playAudioInterruptionBegin() { - print(#function) - // stop play - playingCell?.stopAnimation() - } + public func playAudio(_ filePath: String, progress value: Float) {} + + public func playAudioInterruptionEnd() { + print(#function) + } + + public func playAudioInterruptionBegin() { + print(#function) + // stop play + playingCell?.stopAnimation() + } // record - public func recordAudio(_ filePath: String?, didBeganWithError error: Error?) { - print("[record] sdk Began error:\(error)") + public func recordAudio(_ filePath: String?, didBeganWithError error: Error?) { + print("[record] sdk Began error:\(error)") + } + + public func recordAudio(_ filePath: String?, didCompletedWithError error: Error?) { + print("[record] sdk Completed error:\(error)") + menuView.stopRecordAnimation() + guard let fp = filePath else { + showToast(error?.localizedDescription ?? "") + return } + let dur = recordDuration(filePath: fp) - public func recordAudio(_ filePath: String?, didCompletedWithError error: Error?) { - print("[record] sdk Completed error:\(error)") - menuView.stopRecordAnimation() - guard let fp = filePath else { - showToast(error?.localizedDescription ?? "") - return - } - let dur = recordDuration(filePath: fp) - - print("dur:\(dur)") - if dur > 1 { - viewmodel.sendAudioMessage(filePath: fp) { error in - if let e = error { - self.showToast(e.localizedDescription) - } else {} - } - } else { - showToast(localizable("录音时间太短")) + print("dur:\(dur)") + if dur > 1 { + viewmodel.sendAudioMessage(filePath: fp) { error in + if let e = error { + self.showToast(e.localizedDescription) + } else {} } + } else { + showToast(localizable("record_too_short")) } + } - public func recordAudioDidCancelled() { - print("[record] sdk cancel") - } + public func recordAudioDidCancelled() { + print("[record] sdk cancel") + } - public func recordAudioProgress(_ currentTime: TimeInterval) {} + public func recordAudioProgress(_ currentTime: TimeInterval) {} - public func recordAudioInterruptionBegin() { - print(#function) - } + public func recordAudioInterruptionBegin() { + print(#function) + } // MARK: Private Method - private func recordDuration(filePath: String) -> Float64 { - let avAsset = AVURLAsset(url: URL(fileURLWithPath: filePath)) - return CMTimeGetSeconds(avAsset.duration) - } + private func recordDuration(filePath: String) -> Float64 { + let avAsset = AVURLAsset(url: URL(fileURLWithPath: filePath)) + return CMTimeGetSeconds(avAsset.duration) + } - private func insertRows() { - let oldRows = tableView.numberOfRows(inSection: 0) - if oldRows == 0 { - tableView.reloadData() - return - } - if oldRows == viewmodel.messages.count { - tableView.reloadData() - return - } - var indexs = [IndexPath]() - for (i, model) in viewmodel.messages.enumerated() { - if i >= oldRows { - indexs.append(IndexPath(row: i, section: 0)) - } + private func insertRows() { + let oldRows = tableView.numberOfRows(inSection: 0) + if oldRows == 0 { + tableView.reloadData() + return + } + if oldRows == viewmodel.messages.count { + tableView.reloadData() + return + } + var indexs = [IndexPath]() + for (i, model) in viewmodel.messages.enumerated() { + if i >= oldRows { + indexs.append(IndexPath(row: i, section: 0)) } + } - print("oo indexs:\(indexs)") - print("oo:\(viewmodel.messages.count)") - if !indexs.isEmpty { - tableView.insertRows(at: indexs, with: .none) - tableView.scrollToRow( - at: IndexPath(row: viewmodel.messages.count - 1, section: 0), - at: .bottom, - animated: false - ) - } + if !indexs.isEmpty { + tableView.insertRows(at: indexs, with: .none) + tableView.scrollToRow( + at: IndexPath(row: viewmodel.messages.count - 1, section: 0), + at: .bottom, + animated: false + ) } + } - private func showUserSelectVC(text: String) { - let selectVC = SelectUserViewController(sessionId: viewmodel.session.sessionId) - selectVC.modalPresentationStyle = .formSheet - selectVC.selectedBlock = { [weak self] index, model in - var resultText = "" - var location = 0 - var length = 0 - if let t = self?.menuView.textField.text, t.count > 0 { - resultText = t - location = t.count - } - if index == 0 { - let addText = text + localizable("user_select_all") + " " + private func showUserSelectVC(text: String) { + let selectVC = SelectUserViewController(sessionId: viewmodel.session.sessionId) + selectVC.modalPresentationStyle = .formSheet + selectVC.selectedBlock = { [weak self] index, model in + var resultText = "" + var location = 0 + var length = 0 + if let t = self?.menuView.textField.text, t.count > 0 { + resultText = t + location = t.count + } + if index == 0 { + let addText = text + localizable("user_select_all") + " " + resultText = resultText + addText + length = addText.count + let range = NSRange(location: location, length: length) + self?.atUsers.append(range) + } else { + if let m = model { + let addText = text + m.atNameInTeam() + " " resultText = resultText + addText length = addText.count let range = NSRange(location: location, length: length) self?.atUsers.append(range) - } else { - if let m = model { - let addText = text + m.atNameInTeam() + " " - resultText = resultText + addText - length = addText.count - let range = NSRange(location: location, length: length) - self?.atUsers.append(range) - } } - self?.menuView.textField.text = resultText } - present(selectVC, animated: true, completion: nil) + self?.menuView.textField.text = resultText } + present(selectVC, animated: true, completion: nil) + } // MARK: MessageOperationViewDelegate - public func didSelectedItem(item: OperationItem) { - switch item.type { - case .copy: - copyMessage() - case .delete: - deleteMessage() - case .reply: - showReplyMessageView() - case .recall: - recallMessage() - case .collection: - collectionMessage() - case .forward: - forwardMessage() - case .pin: - pinMessage() - case .removePin: - removePinMessage() - default: - doNothing() - } + public func didSelectedItem(item: OperationItem) { + switch item.type { + case .copy: + copyMessage() + case .delete: + deleteMessage() + case .reply: + showReplyMessageView() + case .recall: + recallMessage() + case .collection: + collectionMessage() + case .forward: + forwardMessage() + case .pin: + pinMessage() + case .removePin: + removePinMessage() + default: + doNothing() } + } - private func doNothing() {} + private func doNothing() {} - private func copyMessage() { - if let model = viewmodel.operationModel as? MessageTextModel, - let text = model.attributeStr { - let pasteboard = UIPasteboard.general - pasteboard.string = text.string - showToast(localizable("copy_success")) - } + private func copyMessage() { + if let model = viewmodel.operationModel as? MessageTextModel, + let text = model.attributeStr { + let pasteboard = UIPasteboard.general + pasteboard.string = text.string + showToast(localizable("copy_success")) } + } - private func deleteMessage() { - showAlert(message: localizable("message_delete_comfirm")) { - if let message = self.viewmodel.operationModel?.message { - self.viewmodel.deleteMessage(message: message) - } + private func deleteMessage() { + showAlert(message: localizable("message_delete_comfirm")) { + if let message = self.viewmodel.operationModel?.message { + self.viewmodel.deleteMessage(message: message) } } + } - private func showReplyMessageView() { - viewmodel.isReplying = true - view.addSubview(replyView) - replyView.closeButton.addTarget(self, action: #selector(cancelReply), for: .touchUpInside) - replyView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - replyView.leadingAnchor.constraint(equalTo: menuView.leadingAnchor), - replyView.trailingAnchor.constraint(equalTo: menuView.trailingAnchor), - replyView.bottomAnchor.constraint(equalTo: menuView.topAnchor), - replyView.heightAnchor.constraint(equalToConstant: 36), - ]) - if let message = viewmodel.operationModel?.message { - var text = localizable("msg_reply") - if let name = viewmodel.operationModel?.shortName { - text = text + name - } - text = text + ":" - switch message.messageType { - case .text: - if let t = message.text { - text = text + t - } - case .image: - text = text + localizable("msg_image") - case .audio: - text = text + localizable("msg_audio") - case .video: - text = text + localizable("msg_video") - default: - text = text + "" - } - replyView.textLabel.text = text + private func showReplyMessageView() { + viewmodel.isReplying = true + view.addSubview(replyView) + replyView.closeButton.addTarget(self, action: #selector(cancelReply), for: .touchUpInside) + replyView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + replyView.leadingAnchor.constraint(equalTo: menuView.leadingAnchor), + replyView.trailingAnchor.constraint(equalTo: menuView.trailingAnchor), + replyView.bottomAnchor.constraint(equalTo: menuView.topAnchor), + replyView.heightAnchor.constraint(equalToConstant: 36), + ]) + if let message = viewmodel.operationModel?.message { + var text = localizable("msg_reply") + if let name = viewmodel.operationModel?.shortName { + text = text + name } - } - - @objc private func cancelReply(button: UIButton) { - replyView.removeFromSuperview() - viewmodel.isReplying = false - } - - private func recallMessage() { - showAlert(message: localizable("message_revoke_confim")) { - if let message = self.viewmodel.operationModel?.message { - self.viewmodel.revokeMessage(message: message) { error in - if error != nil { - self.showToast(error!.localizedDescription) - } else { - // 自己撤回成功 & 收到对方撤回 都会走回调方法 onRevokeMessage - // 撤回成功的逻辑统一在代理方法中处理 onRevokeMessage - } - } + text = text + ":" + switch message.messageType { + case .text: + if let t = message.text { + text = text + t } + case .image: + text = text + localizable("msg_image") + case .audio: + text = text + localizable("msg_audio") + case .video: + text = text + localizable("msg_video") + default: + text = text + "" } + replyView.textLabel.text = text } + } - private func collectionMessage() { - if let message = viewmodel.operationModel?.message { - viewmodel.addColletion(message) { error, info in + @objc private func cancelReply(button: UIButton) { + replyView.removeFromSuperview() + viewmodel.isReplying = false + } + + private func recallMessage() { + showAlert(message: localizable("message_revoke_confim")) { + if let message = self.viewmodel.operationModel?.message { + self.viewmodel.revokeMessage(message: message) { error in if error != nil { self.showToast(error!.localizedDescription) } else { - self.showToast(localizable("collection_success")) + // 自己撤回成功 & 收到对方撤回 都会走回调方法 onRevokeMessage + // 撤回成功的逻辑统一在代理方法中处理 onRevokeMessage } } } } + } - private func forwardMessage() { - if let message = viewmodel.operationModel?.message { - weak var weakSelf = self - let userAction = UIAlertAction(title: localizable("contact_user"), - style: .default) { action in - - Router.shared.register(ContactSelectedUsersRouter) { param in - print("user setting accids : ", param) - var items = [ForwardItem]() - - if let users = param["im_user"] as? [NIMUser] { - users.forEach { user in - let item = ForwardItem() - item.uid = user.userId - item.avatar = user.userInfo?.avatarUrl - item.name = user.userInfo?.nickName - items.append(item) - } - - let forwardAlert = ForwardAlertViewController() - forwardAlert.setItems(items) - if let senderName = message.senderName { - forwardAlert.context = senderName - } - weakSelf?.addChild(forwardAlert) - weakSelf?.view.addSubview(forwardAlert.view) - - forwardAlert.sureBlock = { - print("sure click ") - weakSelf?.viewmodel.forwardUserMessage(message, users) - } - } - } - var param = [String: Any]() - param["nav"] = weakSelf?.navigationController as Any - param["limit"] = 6 - if let session = weakSelf?.viewmodel.session, session.sessionType == .P2P { - var filters = Set() - filters.insert(session.sessionId) - param["filters"] = filters - } - Router.shared.use(ContactUserSelectRouter, parameters: param, closure: nil) + private func collectionMessage() { + if let message = viewmodel.operationModel?.message { + viewmodel.addColletion(message) { error, info in + if error != nil { + self.showToast(error!.localizedDescription) + } else { + self.showToast(localizable("collection_success")) } + } + } + } + + private func forwardMessage() { + if let message = viewmodel.operationModel?.message { + weak var weakSelf = self + let userAction = UIAlertAction(title: localizable("contact_user"), + style: .default) { action in - let teamAction = UIAlertAction(title: localizable("team"), style: .default) { action in + Router.shared.register(ContactSelectedUsersRouter) { param in + print("user setting accids : ", param) + var items = [ForwardItem]() - Router.shared.register(ContactTeamDataRouter) { param in - if let team = param["team"] as? NIMTeam { + if let users = param["im_user"] as? [NIMUser] { + users.forEach { user in let item = ForwardItem() - item.avatar = team.avatarUrl - item.name = team.getShowName() - item.uid = team.teamId - - let forwardAlert = ForwardAlertViewController() - forwardAlert.setItems([item]) - if let senderName = message.senderName { - forwardAlert.context = senderName - } - weakSelf?.addChild(forwardAlert) - weakSelf?.view.addSubview(forwardAlert.view) - weakSelf?.viewmodel.forwardTeamMessage(message, team) + item.uid = user.userId + item.avatar = user.userInfo?.avatarUrl + item.name = user.userInfo?.nickName + items.append(item) } - } - Router.shared.use( - ContactTeamListRouter, - parameters: ["nav": weakSelf?.navigationController as Any], - closure: nil - ) - } + let forwardAlert = ForwardAlertViewController() + forwardAlert.setItems(items) + if let senderName = message.senderName { + forwardAlert.context = senderName + } + weakSelf?.addChild(forwardAlert) + weakSelf?.view.addSubview(forwardAlert.view) - let cancelAction = UIAlertAction(title: localizable("cancel"), - style: .cancel) { action in + forwardAlert.sureBlock = { + print("sure click ") + weakSelf?.viewmodel.forwardUserMessage(message, users) + } + } } - - showActionSheet([userAction, teamAction, cancelAction]) + var param = [String: Any]() + param["nav"] = weakSelf?.navigationController as Any + param["limit"] = 6 + if let session = weakSelf?.viewmodel.session, session.sessionType == .P2P { + var filters = Set() + filters.insert(session.sessionId) + param["filters"] = filters + } + Router.shared.use(ContactUserSelectRouter, parameters: param, closure: nil) } - } - private func pinMessage() { - if let message = viewmodel.operationModel?.message { - viewmodel.pinMessage(message) { [weak self] error, pinItem, index in - if error != nil { - self?.view.makeToast(error?.localizedDescription) - } else { - // update UI - if index >= 0 { - self?.tableViewReloadIndexs([IndexPath(row: index, section: 0)]) + let teamAction = UIAlertAction(title: localizable("team"), style: .default) { action in + + Router.shared.register(ContactTeamDataRouter) { param in + if let team = param["team"] as? NIMTeam { + let item = ForwardItem() + item.avatar = team.avatarUrl + item.name = team.getShowName() + item.uid = team.teamId + + let forwardAlert = ForwardAlertViewController() + forwardAlert.setItems([item]) + if let senderName = message.senderName { + forwardAlert.context = senderName } + weakSelf?.addChild(forwardAlert) + weakSelf?.view.addSubview(forwardAlert.view) + weakSelf?.viewmodel.forwardTeamMessage(message, team) } } + + Router.shared.use( + ContactTeamListRouter, + parameters: ["nav": weakSelf?.navigationController as Any], + closure: nil + ) + } + + let cancelAction = UIAlertAction(title: localizable("cancel"), + style: .cancel) { action in } + + showActionSheet([userAction, teamAction, cancelAction]) } + } - private func removePinMessage() { - if let message = viewmodel.operationModel?.message { - viewmodel.removePinMessage(message) { [weak self] error, pinItem, index in - if error != nil { - self?.view.makeToast(error?.localizedDescription) - } else { - // update UI - if index >= 0 { - self?.tableViewReloadIndexs([IndexPath(row: index, section: 0)]) - } + private func pinMessage() { + if let message = viewmodel.operationModel?.message { + viewmodel.pinMessage(message) { [weak self] error, pinItem, index in + if error != nil { + self?.view.makeToast(error?.localizedDescription) + } else { + // update UI + if index >= 0 { + self?.tableViewReloadIndexs([IndexPath(row: index, section: 0)]) } } } } - - //MARK: UITableViewDataSource, UITableViewDelegate - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - viewmodel.messages.count ?? 0 - } + } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let model = viewmodel.messages[indexPath.row] - var reuseId = "" - if let isSend = model.message?.isOutgoingMsg, isSend { - if model.replyedModel != nil { - reuseId = "\(ChatReplyRightCell.self)" + private func removePinMessage() { + if let message = viewmodel.operationModel?.message { + viewmodel.removePinMessage(message) { [weak self] error, pinItem, index in + if error != nil { + self?.view.makeToast(error?.localizedDescription) } else { - switch model.type { - case .text: - reuseId = "\(ChatTextRightCell.self)" - case .image: - reuseId = "\(ChatImageRightCell.self)" - case .audio: - reuseId = "\(ChatAudioRightCell.self)" - case .video: - reuseId = "\(ChatVideoRightCell.self)" - case .time, .tip, .notification: - reuseId = "\(ChatTimeTableViewCell.self)" - case .revoke: - reuseId = "\(ChatRevokeRightCell.self)" - default: - reuseId = "\(ChatBaseRightCell.self)" + // update UI + if index >= 0 { + self?.tableViewReloadIndexs([IndexPath(row: index, section: 0)]) } } + } + } + } - let cell = tableView.dequeueReusableCell(withIdentifier: reuseId, for: indexPath) - if let c = cell as? ChatTimeTableViewCell { - if let m = model as? MessageTipsModel { - c.setModel(m) - } - return c - } else if let c = cell as? ChatBaseRightCell { - c.delegate = self - if let m = model as? MessageContentModel { - c.setModel(m) - } - return c - } else { - return ChatBaseRightCell() - } + // MARK: UITableViewDataSource, UITableViewDelegate + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + viewmodel.messages.count ?? 0 + } + + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let model = viewmodel.messages[indexPath.row] + var reuseId = "" + if let isSend = model.message?.isOutgoingMsg, isSend { + if model.replyedModel != nil { + reuseId = "\(ChatReplyRightCell.self)" } else { - if model.replyedModel != nil { - reuseId = "\(ChatReplyLeftCell.self)" - } else { - switch model.type { - case .text: - reuseId = "\(ChatTextLeftCell.self)" - case .image: - reuseId = "\(ChatImageLeftCell.self)" - case .audio: - reuseId = "\(ChatAudioLeftCell.self)" - case .video: - reuseId = "\(ChatVideoLeftCell.self)" - case .time, .tip, .notification: - reuseId = "\(ChatTimeTableViewCell.self)" - case .revoke: - reuseId = "\(ChatRevokeLeftCell.self)" - default: - reuseId = "\(ChatBaseLeftCell.self)" - } + switch model.type { + case .text: + reuseId = "\(ChatTextRightCell.self)" + case .image: + reuseId = "\(ChatImageRightCell.self)" + case .audio: + reuseId = "\(ChatAudioRightCell.self)" + case .video: + reuseId = "\(ChatVideoRightCell.self)" + case .time, .tip, .notification: + reuseId = "\(ChatTimeTableViewCell.self)" + case .revoke: + reuseId = "\(ChatRevokeRightCell.self)" + default: + reuseId = "\(ChatBaseRightCell.self)" } + } - let cell = tableView.dequeueReusableCell(withIdentifier: reuseId, for: indexPath) - if let c = cell as? ChatTimeTableViewCell { - if let m = model as? MessageTipsModel { - c.setModel(m) - } - return c - } else if let c = cell as? ChatBaseLeftCell { - c.delegate = self - if let m = model as? MessageContentModel { - c.setModel(m) - } - return c - } else { - return ChatBaseLeftCell() + let cell = tableView.dequeueReusableCell(withIdentifier: reuseId, for: indexPath) + if let c = cell as? ChatTimeTableViewCell { + if let m = model as? MessageTipsModel { + c.setModel(m) + } + return c + } else if let c = cell as? ChatBaseRightCell { + c.delegate = self + if let m = model as? MessageContentModel { + c.setModel(m) + } + return c + } else { + return ChatBaseRightCell() + } + } else { + if model.replyedModel != nil { + reuseId = "\(ChatReplyLeftCell.self)" + } else { + switch model.type { + case .text: + reuseId = "\(ChatTextLeftCell.self)" + case .image: + reuseId = "\(ChatImageLeftCell.self)" + case .audio: + reuseId = "\(ChatAudioLeftCell.self)" + case .video: + reuseId = "\(ChatVideoLeftCell.self)" + case .time, .tip, .notification: + reuseId = "\(ChatTimeTableViewCell.self)" + case .revoke: + reuseId = "\(ChatRevokeLeftCell.self)" + default: + reuseId = "\(ChatBaseLeftCell.self)" } } - } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - print("did select row ") - operationView?.removeFromSuperview() - if menuView.textField.isFirstResponder { - menuView.textField.resignFirstResponder() + let cell = tableView.dequeueReusableCell(withIdentifier: reuseId, for: indexPath) + if let c = cell as? ChatTimeTableViewCell { + if let m = model as? MessageTipsModel { + c.setModel(m) + } + return c + } else if let c = cell as? ChatBaseLeftCell { + c.delegate = self + if let m = model as? MessageContentModel { + c.setModel(m) + } + return c } else { - layoutInputView(offset: 0) + return ChatBaseLeftCell() } } + } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - let m = viewmodel.messages[indexPath.row] - print("text:\(m.message?.text) height:\(m.height)") - return CGFloat(m.height) + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + print("did select row ") + operationView?.removeFromSuperview() + if menuView.textField.isFirstResponder { + menuView.textField.resignFirstResponder() + } else { + layoutInputView(offset: 0) } - + } + + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + let m = viewmodel.messages[indexPath.row] + print("text:\(m.message?.text) height:\(m.height)") + return CGFloat(m.height) + } } // MARK: ChatBaseCellDelegate @@ -1460,7 +1461,3 @@ extension ChatViewController: ChatBaseCellDelegate { } } } - - - - diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/GroupChatViewController.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/GroupChatViewController.swift index 1e23898e..76db717f 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/GroupChatViewController.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/GroupChatViewController.swift @@ -56,7 +56,7 @@ open class GroupChatViewController: ChatViewController, TeamChatViewModelDelegat public func onTeamRemoved(team: NIMTeam) { // 只有群创建者 才弹弹窗 - if team.owner == IMKitLoginManager.instance.imAccid { + if team.owner == IMKitEngine.instance.imAccid { showSingleAlert(message: localizable("team_has_been_removed")) { self.navigationController?.popViewController(animated: true) } diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/P2PChatViewController.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/P2PChatViewController.swift index 17e13540..e16e38cd 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/P2PChatViewController.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/P2PChatViewController.swift @@ -20,7 +20,6 @@ open class P2PChatViewController: ChatViewController { let title = user?.showName() ?? "" self.title = title titleContent = title - // self.menuView.textField.placeholder = localizable("send_to") + title menuView.textField.placeholder = localizable("send_to") + title as NSString } diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/ReadViewController.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/ReadViewController.swift index 3d1da908..dbb8b596 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/ReadViewController.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/ReadViewController.swift @@ -8,7 +8,7 @@ import NIMSDK import NEKitCoreIM import NEKitCommonUI -public class ReadViewController: NEBaseViewController, UIScrollViewDelegate, UITableViewDelegate, +public class ReadViewController: ChatBaseViewController, UIScrollViewDelegate, UITableViewDelegate, UITableViewDataSource { public var read: Bool = true public var line: UIView = .init() @@ -37,7 +37,7 @@ public class ReadViewController: NEBaseViewController, UIScrollViewDelegate, UIT func commonUI() { title = localizable("message_read") readButton.titleLabel?.font = UIFont.systemFont(ofSize: 14) - readButton.setTitle("已读(0)", for: .normal) + readButton.setTitle(localizable("read"), for: .normal) readButton.setTitleColor(UIColor.ne_darkText, for: .normal) readButton.translatesAutoresizingMaskIntoConstraints = false readButton.addTarget(self, action: #selector(readButtonEvent), for: .touchUpInside) @@ -45,7 +45,7 @@ public class ReadViewController: NEBaseViewController, UIScrollViewDelegate, UIT unreadButton.titleLabel?.font = UIFont.systemFont(ofSize: 14) unreadButton.setTitleColor(UIColor.ne_darkText, for: .normal) - unreadButton.setTitle("未读(0)", for: .normal) + unreadButton.setTitle(localizable("unread"), for: .normal) unreadButton.translatesAutoresizingMaskIntoConstraints = false unreadButton.addTarget(self, action: #selector(unreadButtonEvent), for: .touchUpInside) diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/SelectUserViewController.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/SelectUserViewController.swift index 721ce48e..80be5a1d 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/SelectUserViewController.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/SelectUserViewController.swift @@ -8,7 +8,7 @@ import NEKitChat public typealias didSelectedAtRow = (_ index: Int, _ model: ChatTeamMemberInfoModel?) -> Void -public class SelectUserViewController: NEBaseViewController, UITableViewDelegate, +public class SelectUserViewController: ChatBaseViewController, UITableViewDelegate, UITableViewDataSource { public var tableView = UITableView(frame: .zero, style: .plain) public var sessionId: String diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/UserSettingViewController.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/UserSettingViewController.swift index 56bbc010..af0763a3 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/UserSettingViewController.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/Controller/UserSettingViewController.swift @@ -7,7 +7,8 @@ import UIKit import NEKitCommon import NIMSDK -public class UserSettingViewController: NEBaseViewController, UserSettingViewModelDelegate,UITableViewDataSource, UITableViewDelegate{ +public class UserSettingViewController: ChatBaseViewController, UserSettingViewModelDelegate, + UITableViewDataSource, UITableViewDelegate { var userId: String? let viewmodel = UserSettingViewModel() @@ -157,7 +158,12 @@ public class UserSettingViewController: NEBaseViewController, UserSettingViewMod Router.shared.use( ContactUserSelectRouter, - parameters: ["nav": navigationController as Any, "filters": filters, "limit": 199], + parameters: [ + "nav": navigationController as Any, + "filters": filters, + "limit": 199, + "uid": userId ?? "", + ], closure: nil ) @@ -209,27 +215,26 @@ public class UserSettingViewController: NEBaseViewController, UserSettingViewMod showToast(error.localizedDescription) } - //MARK: UITableViewDataSource, UITableViewDelegate - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - viewmodel.cellDatas.count - } + // MARK: UITableViewDataSource, UITableViewDelegate - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let model = viewmodel.cellDatas[indexPath.row] - if let cell = tableView.dequeueReusableCell( - withIdentifier: "\(UserSettingSwitchCell.self)", - for: indexPath - ) as? UserSettingBaseCell { - cell.configure(model) - return cell - } - return UITableViewCell() - } + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + viewmodel.cellDatas.count + } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - // let model = viewmodel.cellDatas[indexPath.row] + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let model = viewmodel.cellDatas[indexPath.row] + if let cell = tableView.dequeueReusableCell( + withIdentifier: "\(UserSettingSwitchCell.self)", + for: indexPath + ) as? UserSettingBaseCell { + cell.configure(model) + return cell } -} - + return UITableViewCell() + } + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + // let model = viewmodel.cellDatas[indexPath.row] + } +} diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/Emoji/InputEmoticonTabView.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/Emoji/InputEmoticonTabView.swift index 398168de..b1aa08a1 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/Emoji/InputEmoticonTabView.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/Emoji/InputEmoticonTabView.swift @@ -82,7 +82,7 @@ public class InputEmoticonTabView: UIControl { public lazy var sendButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false - button.setTitle("发送", for: .normal) + button.setTitle(localizable("send"), for: .normal) button.titleLabel?.textColor = .white button.backgroundColor = UIColor.ne_blueText button.titleLabel?.font = DefaultTextFont(14) diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/Helper/MessageUtils.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/Helper/MessageUtils.swift index 4d745f29..6e7030eb 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/Helper/MessageUtils.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/Helper/MessageUtils.swift @@ -29,7 +29,7 @@ public class MessageUtils { option.compressQuality = 0.8 imageObject.option = option message.messageObject = imageObject - message.apnsContent = localizable("发来了一张图片") + message.apnsContent = localizable("send_picture") message.setting = messageSetting() return message } @@ -38,7 +38,7 @@ public class MessageUtils { let messageObject = NIMAudioObject(sourcePath: filePath) let message = NIMMessage() message.messageObject = messageObject - message.apnsContent = localizable("发来了一段语音") + message.apnsContent = localizable("send_voice") message.setting = messageSetting() return message } @@ -47,7 +47,7 @@ public class MessageUtils { let messageObject = NIMVideoObject(sourcePath: filePath) let message = NIMMessage() message.messageObject = messageObject - message.apnsContent = localizable("发来了一段视频") + message.apnsContent = localizable("send_video") message.setting = messageSetting() return message } diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/Model/MessageContentModel.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/Model/MessageContentModel.swift index 6e7d4b6a..e987a1a8 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/Model/MessageContentModel.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/Model/MessageContentModel.swift @@ -78,7 +78,7 @@ public class MessageContentModel: MessageModel { self.message = message contentSize = CGSize(width: 32.0, height: qChat_min_h) if message?.session?.sessionType == .team, - !IMKitLoginManager.instance.isMySelf(message?.from) { + !IMKitEngine.instance.isMySelf(message?.from) { fullNameHeight = 20 } print("self.fullNameHeight\(fullNameHeight)") diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/View/Cell/ChatVideoLeftCell.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/View/Cell/ChatVideoLeftCell.swift index 46063252..81432f4d 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/View/Cell/ChatVideoLeftCell.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/View/Cell/ChatVideoLeftCell.swift @@ -81,9 +81,9 @@ class ChatVideoLeftCell: ChatImageLeftCell { override func setModel(_ model: MessageContentModel) { super.setModel(model) if let videoObject = model.message?.messageObject as? NIMVideoObject { - if let path = videoObject.coverPath { + if let path = videoObject.coverUrl { contentImageView.sd_setImage( - with: URL(fileURLWithPath: path), + with: URL(string: path), placeholderImage: nil, options: .retryFailed, progress: nil, diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ChatBrokenNetworkView.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ChatBrokenNetworkView.swift index dd489a16..59a20c4e 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ChatBrokenNetworkView.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ChatBrokenNetworkView.swift @@ -31,7 +31,7 @@ public class ChatBrokenNetworkView: UIView { label.font = DefaultTextFont(14) label.textColor = HexRGB(0xFC596A) label.textAlignment = .center - label.text = "当前网络不可用,请检查你当网络设置。" + label.text = localizable("network_unavailable") return label }() } diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ChatInputView.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ChatInputView.swift index 4bba0287..3e53b34e 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ChatInputView.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ChatInputView.swift @@ -236,18 +236,10 @@ public class ChatInputView: UIView, UITextFieldDelegate, ChatRecordViewDelegate, delegate?.textFieldDidEndEditing(textView) } - public func textFieldDidEndEditing(_ textField: UITextField) { - // delegate?.textFieldDidEndEditing(textField) - } - public func textViewDidBeginEditing(_ textView: UITextView) { delegate?.textFieldDidBeginEditing(textView) } - public func textFieldDidBeginEditing(_ textField: UITextField) { - // delegate?.textFieldDidBeginEditing(textField) - } - public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { currentType = .text return true diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ChatRecordView.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ChatRecordView.swift index 07a39aa8..10211abc 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ChatRecordView.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ChatRecordView.swift @@ -63,7 +63,7 @@ class ChatRecordView: UIView, UIGestureRecognizerDelegate { recordImageView.addGestureRecognizer(guesture) tipLabel.translatesAutoresizingMaskIntoConstraints = false - tipLabel.text = localizable("按住说话") + tipLabel.text = localizable("press_speak") tipLabel.font = UIFont.systemFont(ofSize: 12) tipLabel.textColor = .ne_lightText tipLabel.textAlignment = .center diff --git a/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ForwardAlertViewController.swift b/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ForwardAlertViewController.swift index e34457a1..210aa989 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ForwardAlertViewController.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Chat/View/ForwardAlertViewController.swift @@ -201,13 +201,13 @@ public class ForwardAlertViewController: UIViewController { let canceBtn = UIButton() canceBtn.translatesAutoresizingMaskIntoConstraints = false canceBtn.addTarget(self, action: #selector(cancelClick), for: .touchUpInside) - canceBtn.setTitle("取消", for: .normal) + canceBtn.setTitle(localizable("cancel"), for: .normal) canceBtn.setTitleColor(.ne_greyText, for: .normal) let sureBtn = UIButton() sureBtn.translatesAutoresizingMaskIntoConstraints = false sureBtn.addTarget(self, action: #selector(sureClick), for: .touchUpInside) - sureBtn.setTitle("确定", for: .normal) + sureBtn.setTitle(localizable("confirm"), for: .normal) sureBtn.setTitleColor(.ne_blueText, for: .normal) contentView.addSubview(canceBtn) diff --git a/NEChatUIKit/NEKitChatUI/Classes/ChatRouter/ChatRouter.swift b/NEChatUIKit/NEKitChatUI/Classes/ChatRouter/ChatRouter.swift index ad31045d..9bd036b3 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/ChatRouter/ChatRouter.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/ChatRouter/ChatRouter.swift @@ -7,7 +7,8 @@ import Foundation import NIMSDK import NEKitCommon -public enum ChatRouter { +@objcMembers +public class ChatRouter:NSObject { public static func register() { // p2p Router.shared.register(PushP2pChatVCRouter) { param in diff --git a/NEChatUIKit/NEKitChatUI/Classes/Common/NSBundleExtension.swift b/NEChatUIKit/NEKitChatUI/Classes/Common/NSBundleExtension.swift index c3932ff2..c3bbb7f5 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Common/NSBundleExtension.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Common/NSBundleExtension.swift @@ -4,6 +4,7 @@ // found in the LICENSE file. import Foundation +import NEKitCommon public extension Bundle { class func nim_defaultEmojiBundle() -> Bundle? { @@ -18,8 +19,10 @@ public extension Bundle { class func nim_EmojiPlistFile() -> String? { let bundle = Bundle.nim_defaultEmojiBundle() + + let resource = (CommonTool.getCurrentLanguage() == "cn") ?"emoji_ios_cn" : "emoji_ios_en" let filepath = bundle?.path( - forResource: "emoji_ios_new", + forResource: resource, ofType: "plist", inDirectory: NIMKit_EmojiPath ) diff --git a/NEChatUIKit/NEKitChatUI/Classes/Extension/ChatStringExtension.swift b/NEChatUIKit/NEKitChatUI/Classes/Extension/ChatStringExtension.swift index f8d0a42e..bb73c625 100644 --- a/NEChatUIKit/NEKitChatUI/Classes/Extension/ChatStringExtension.swift +++ b/NEChatUIKit/NEKitChatUI/Classes/Extension/ChatStringExtension.swift @@ -23,12 +23,12 @@ extension String { if let firstDayYear = firstDayInYear() { let dur = date.timeIntervalSince(firstDayYear) if dur > 0 { - fmt.dateFormat = "MM月dd日 HH:mm" + fmt.dateFormat = localizable("mdhm") } else { - fmt.dateFormat = "yyyy年MM月dd日 HH:mm" + fmt.dateFormat = localizable("ymdhm") } } else { - fmt.dateFormat = "yyyy年MM月dd日 HH:mm" + fmt.dateFormat = localizable("ymdhm") } } return fmt.string(from: date) diff --git a/NEContactUIKit/NEKitContactUI.podspec b/NEContactUIKit/NEKitContactUI.podspec index 5517d963..7c4090cb 100644 --- a/NEContactUIKit/NEKitContactUI.podspec +++ b/NEContactUIKit/NEKitContactUI.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'NEKitContactUI' - s.version = '9.2.7' + s.version = '9.2.8' s.summary = 'Netease XKit' # This description is used to generate tags and improve search results. @@ -23,12 +23,10 @@ Pod::Spec.new do |s| s.source = { :git => 'ssh://git@g.hz.netease.com:22222/yunxin-app/xkit-ios.git', :tag => s.version.to_s } # s.source = { :git => 'https://github.com/chenyu-home/ContactKitUI.git', :tag => s.version.to_s } - # s.social_media_url = 'https://twitter.com/' s.pod_target_xcconfig = { 'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES' } s.user_target_xcconfig = { 'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES' } - s.ios.deployment_target = '9.0' s.source_files = 'NEKitContactUI/Classes/**/*' diff --git a/NEContactUIKit/NEKitContactUI/Assets/en.lproj/Localizable.strings b/NEContactUIKit/NEKitContactUI/Assets/en.lproj/Localizable.strings index a75d7def..076ea47c 100644 --- a/NEContactUIKit/NEKitContactUI/Assets/en.lproj/Localizable.strings +++ b/NEContactUIKit/NEKitContactUI/Assets/en.lproj/Localizable.strings @@ -3,13 +3,39 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. -"验证消息"="验证消息"; -"alert_tip"="提示"; -"alert_sure"="确定"; -"alert_cancel"="取消"; -"备注名"="备注名"; -"remove_black" = "解除"; +"alert_tip"="tip"; +"alert_sure"="ok"; +"alert_cancel"="cancel"; +"noteName"="Nick Name"; +"remove_black" = "Remove"; + +"blacklist"="Blacklist"; +"mine_groupchat"="My Group"; +"save"="Save"; +"input_noteName"="Please enter nick name"; +"phone"="mobile"; +"email"="e-mail"; +"sign"="What's Up"; +"delete_friend"="Delete Contact"; +"add_friend"="Add Contact"; +"add_blackList"="block"; +"send_friend_apply"="Contact request sent"; +"validation_message"="Verify message"; +"clear"="Clear"; +"agreed"="Added"; +"refused"="Rejected"; +"expired"="Expired"; +"refuse"="Reject"; +"agree"="Agree"; +"select"="Select"; +"select_contact"="Please select Contact"; +"user_not_exist"="No contact"; +"chat"="Chat"; +"message_remind"="Message notification"; +"clear_all_validate_message"="Wether to clear all verification"; +"sure_delte_friend"="Wether to delete Contact?"; + diff --git a/NEContactUIKit/NEKitContactUI/Assets/zh-Hans.lproj/Localizable.strings b/NEContactUIKit/NEKitContactUI/Assets/zh-Hans.lproj/Localizable.strings index a6a97b9c..72e4333e 100644 --- a/NEContactUIKit/NEKitContactUI/Assets/zh-Hans.lproj/Localizable.strings +++ b/NEContactUIKit/NEKitContactUI/Assets/zh-Hans.lproj/Localizable.strings @@ -3,10 +3,35 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. -"验证消息"="验证消息"; "alert_tip"="提示"; "alert_sure"="确定"; "alert_cancel"="取消"; -"备注名"="备注名"; +"noteName"="备注名"; "remove_black" = "解除"; + +"blacklist"="黑名单"; +"mine_groupchat"="我的群聊"; +"save"="保存"; +"input_noteName"="请输入备注名"; +"phone"="手机"; +"email"="邮箱"; +"sign"="个性签名"; +"delete_friend"="删除好友"; +"add_friend"="添加好友"; +"add_blackList"="加入黑名单"; +"send_friend_apply"="好友申请已发送"; +"validation_message"="验证消息"; +"clear"="清空"; +"agreed"="已同意"; +"refused"="已拒绝"; +"expired"="已过期"; +"refuse"="拒绝"; +"agree"="同意"; +"select"="选择"; +"select_contact"="请选择联系人"; +"user_not_exist"="该用户不存在"; +"chat"="聊天"; +"message_remind"="消息提醒"; +"clear_all_validate_message"="是否要清除所有验证消息?"; +"sure_delte_friend"="是否确定删除好友?"; diff --git a/NEContactUIKit/NEKitContactUI/Classes/BlackList/ViewController/BlackListViewController.swift b/NEContactUIKit/NEKitContactUI/Classes/BlackList/ViewController/BlackListViewController.swift index 4d6579d1..67357f1f 100644 --- a/NEContactUIKit/NEKitContactUI/Classes/BlackList/ViewController/BlackListViewController.swift +++ b/NEContactUIKit/NEKitContactUI/Classes/BlackList/ViewController/BlackListViewController.swift @@ -19,7 +19,7 @@ public class BlackListViewController: UIViewController, UITableViewDelegate, UIT } func commonUI() { - title = "黑名单" + title = localizable("blacklist") let image = UIImage.ne_imageNamed(name: "backArrow")?.withRenderingMode(.alwaysOriginal) navigationItem.leftBarButtonItem = UIBarButtonItem( image: image, diff --git a/NEContactUIKit/NEKitContactUI/Classes/Common/ContactRouter.swift b/NEContactUIKit/NEKitContactUI/Classes/Common/ContactRouter.swift index a003a280..9e97dece 100644 --- a/NEContactUIKit/NEKitContactUI/Classes/Common/ContactRouter.swift +++ b/NEContactUIKit/NEKitContactUI/Classes/Common/ContactRouter.swift @@ -8,7 +8,8 @@ import NEKitCore import NEKitCoreIM import NIMSDK -public enum ContactRouter { +@objcMembers +public class ContactRouter:NSObject { public static func register() { Router.shared.register(ContactUserSelectRouter) { param in print("param:\(param)") @@ -20,6 +21,9 @@ public enum ContactRouter { if let limit = param["limit"] as? Int, limit > 0 { contactSelectVC.limit = limit } + if let uid = param["uid"] as? String { + contactSelectVC.userId = uid + } nav?.pushViewController(contactSelectVC, animated: true) } diff --git a/NEContactUIKit/NEKitContactUI/Classes/Team/ViewController/TeamListViewController.swift b/NEContactUIKit/NEKitContactUI/Classes/Team/ViewController/TeamListViewController.swift index e7478f9f..927056b8 100644 --- a/NEContactUIKit/NEKitContactUI/Classes/Team/ViewController/TeamListViewController.swift +++ b/NEContactUIKit/NEKitContactUI/Classes/Team/ViewController/TeamListViewController.swift @@ -19,7 +19,7 @@ public class TeamListViewController: UIViewController, UITableViewDelegate, UITa } func commonUI() { - title = "我的群聊" + title = localizable("mine_groupchat") let image = UIImage.ne_imageNamed(name: "backArrow")?.withRenderingMode(.alwaysOriginal) navigationItem.leftBarButtonItem = UIBarButtonItem( image: image, diff --git a/NEContactUIKit/NEKitContactUI/Classes/UserInfo/ViewController/ContactRemakNameViewController.swift b/NEContactUIKit/NEKitContactUI/Classes/UserInfo/ViewController/ContactRemakNameViewController.swift index a3188ed2..e8d9b481 100644 --- a/NEContactUIKit/NEKitContactUI/Classes/UserInfo/ViewController/ContactRemakNameViewController.swift +++ b/NEContactUIKit/NEKitContactUI/Classes/UserInfo/ViewController/ContactRemakNameViewController.swift @@ -37,16 +37,16 @@ public class ContactRemakNameViewController: ContactBaseViewController, UITextFi func setupUI() { view.backgroundColor = UIColor(hexString: "F2F4F5") let clearItem = UIBarButtonItem( - title: "保存", + title: localizable("save"), style: .done, target: self, action: #selector(saveAlias) ) clearItem.tintColor = UIColor(hexString: "337EFF") navigationItem.rightBarButtonItem = clearItem - title = localizable("备注名") + title = localizable("noteName") view.addSubview(aliasInput) - aliasInput.placeholder = "输入备注名" + aliasInput.placeholder = localizable("input_noteName") NSLayoutConstraint.activate([ aliasInput.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20), aliasInput.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20), diff --git a/NEContactUIKit/NEKitContactUI/Classes/UserInfo/ViewController/ContactUserViewController.swift b/NEContactUIKit/NEKitContactUI/Classes/UserInfo/ViewController/ContactUserViewController.swift index 057ca4da..c112da0e 100644 --- a/NEContactUIKit/NEKitContactUI/Classes/UserInfo/ViewController/ContactUserViewController.swift +++ b/NEContactUIKit/NEKitContactUI/Classes/UserInfo/ViewController/ContactUserViewController.swift @@ -105,44 +105,44 @@ public class ContactUserViewController: ContactBaseViewController, UITableViewDe if isFriend { data = [ [UserItem( - title: localizable("备注名"), + title: localizable("noteName"), detailTitle: user?.alias, value: false, textColor: UIColor.darkText, cellClass: TextWithRightArrowCell.self )], [ - UserItem(title: localizable("手机"), detailTitle: user?.userInfo?.mobile, + UserItem(title: localizable("phone"), detailTitle: user?.userInfo?.mobile, value: false, textColor: UIColor.darkText, cellClass: TextWithDetailTextCell.self), UserItem( - title: localizable("邮箱"), + title: localizable("email"), detailTitle: user?.userInfo?.email, value: false, textColor: UIColor.darkText, cellClass: TextWithDetailTextCell.self ), UserItem( - title: localizable("个性签名"), + title: localizable("sign"), detailTitle: user?.userInfo?.sign, value: false, textColor: UIColor.darkText, cellClass: TextWithDetailTextCell.self ), ], - // UserItem(title: localizable("消息提醒"), detailTitle: "", value: false, textColor: UIColor.darkText, cellClass: TextWithSwitchCell.self) 隐藏消息提醒 + [UserItem( - title: localizable("加入黑名单"), + title: localizable("add_blackList"), detailTitle: "", value: isBlack, textColor: UIColor.darkText, cellClass: TextWithSwitchCell.self )], [ - UserItem(title: localizable("聊天"), detailTitle: "", value: false, + UserItem(title: localizable("chat"), detailTitle: "", value: false, textColor: UIColor(hexString: "#337EFF"), cellClass: CenterTextCell.self), UserItem( - title: localizable("删除好友"), + title: localizable("delete_friend"), detailTitle: "", value: false, textColor: UIColor.red, @@ -152,7 +152,7 @@ public class ContactUserViewController: ContactBaseViewController, UITableViewDe ] } else { data = [[UserItem( - title: localizable("添加好友"), + title: localizable("add_friend"), detailTitle: user?.alias, value: false, textColor: UIColor(hexString: "#337EFF"), @@ -194,9 +194,9 @@ public class ContactUserViewController: ContactBaseViewController, UITableViewDe c.switchButton.isOn = item.value c.block = { [weak self] title, value in print("title:\(title) value\(value)") - if title == localizable("加入黑名单") { + if title == localizable("add_blackList") { self?.blackList(isBlack: value) - } else if title == localizable("消息提醒") {} + } else if title == localizable("message_remind") {} } return c @@ -212,7 +212,7 @@ public class ContactUserViewController: ContactBaseViewController, UITableViewDe public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let item = data[indexPath.section][indexPath.row] - if item.title == localizable("备注名") { + if item.title == localizable("noteName") { toEditRemarks() } // if item.title == localizable("消息提醒") { @@ -221,13 +221,13 @@ public class ContactUserViewController: ContactBaseViewController, UITableViewDe // if item.title == localizable("加入黑名单") { // blackList(isBlack: item.value) // } - if item.title == localizable("聊天") { + if item.title == localizable("chat") { chat(user: user) } - if item.title == localizable("删除好友") { + if item.title == localizable("delete_friend") { deleteFriend(user: user) } - if item.title == localizable("添加好友") { + if item.title == localizable("add_friend") { addFriend() } } @@ -293,16 +293,22 @@ public class ContactUserViewController: ContactBaseViewController, UITableViewDe } func deleteFriend(user: User?) { - print("edit remarks") - if let userId = user?.userId { - viewModel.deleteFriend(account: userId) { error in - if error != nil { - self.showToast(error?.localizedDescription ?? "") - } else { - self.navigationController?.popViewController(animated: true) + showAlert( + title: localizable("sure_delte_friend"), + message: "", + sureText: localizable("alert_sure"), + cancelText: localizable("alert_cancel") + ) { [self] in + if let userId = user?.userId { + viewModel.deleteFriend(account: userId) { error in + if error != nil { + self.showToast(error?.localizedDescription ?? "") + } else { + self.navigationController?.popViewController(animated: true) + } } } - } + } cancelBack: {} } @objc func addFriend() { @@ -312,7 +318,7 @@ public class ContactUserViewController: ContactBaseViewController, UITableViewDe if let err = error { NELog.errorLog("ContactUserViewController", desc: "❌add friend failed :\(err)") } else { - weakSelf?.showToast("好友申请已发送") + weakSelf?.showToast(localizable("send_friend_apply")) } } } diff --git a/NEContactUIKit/NEKitContactUI/Classes/Validation/ViewController/ValidationMessageViewController.swift b/NEContactUIKit/NEKitContactUI/Classes/Validation/ViewController/ValidationMessageViewController.swift index 2f3cb736..fd2c614d 100644 --- a/NEContactUIKit/NEKitContactUI/Classes/Validation/ViewController/ValidationMessageViewController.swift +++ b/NEContactUIKit/NEKitContactUI/Classes/Validation/ViewController/ValidationMessageViewController.swift @@ -19,7 +19,7 @@ public class ValidationMessageViewController: ContactBaseViewController { super.viewDidLoad() // Do any additional setup after loading the view. - title = localizable("验证消息") + title = localizable("validation_message") // viewModel.getValidationMessage() setupUI() weak var weakSelf = self @@ -34,7 +34,7 @@ public class ValidationMessageViewController: ContactBaseViewController { func setupUI() { let clearItem = UIBarButtonItem( - title: "清空", + title: localizable("clear"), style: .done, target: self, action: #selector(clearMessage) @@ -63,7 +63,7 @@ public class ValidationMessageViewController: ContactBaseViewController { @objc func clearMessage() { weak var weakSelf = self - showAlert(message: "是否要清除所有验证消息?") { + showAlert(message: localizable("clear_all_validate_message")) { weakSelf?.viewModel.clearAllNoti { weakSelf?.tableView.reloadData() } diff --git a/NEContactUIKit/NEKitContactUI/Classes/Validation/Views/SystemNotificationCell.swift b/NEContactUIKit/NEKitContactUI/Classes/Validation/Views/SystemNotificationCell.swift index 11638cd3..4584497f 100644 --- a/NEContactUIKit/NEKitContactUI/Classes/Validation/Views/SystemNotificationCell.swift +++ b/NEContactUIKit/NEKitContactUI/Classes/Validation/Views/SystemNotificationCell.swift @@ -85,11 +85,11 @@ class SystemNotificationCell: BaseValidationCell { switch notifModel?.handleStatus { case .HandleTypeOk: - resultLabel.text = "已同意" + resultLabel.text = localizable("agreed") case .HandleTypeNo: - resultLabel.text = "已拒绝" + resultLabel.text = localizable("refused") case .HandleTypeOutOfDate: - resultLabel.text = "已过期" + resultLabel.text = localizable("expired") default: resultLabel.text = "" } @@ -137,7 +137,7 @@ class SystemNotificationCell: BaseValidationCell { var rejectBtn: ExpandButton = { let button = ExpandButton() button.translatesAutoresizingMaskIntoConstraints = false - button.setTitle("拒绝", for: .normal) + button.setTitle(localizable("refuse"), for: .normal) button.setTitleColor(UIColor(hexString: "333333"), for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 14.0) button.clipsToBounds = false @@ -150,7 +150,7 @@ class SystemNotificationCell: BaseValidationCell { var agreeBtn: ExpandButton = { let button = ExpandButton() button.translatesAutoresizingMaskIntoConstraints = false - button.setTitle("同意", for: .normal) + button.setTitle(localizable("agree"), for: .normal) let blue = UIColor(hexString: "337EFF") button.setTitleColor(blue, for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 14) diff --git a/NEContactUIKit/NEKitContactUI/Classes/Views/ContactsSelectedViewController.swift b/NEContactUIKit/NEKitContactUI/Classes/Views/ContactsSelectedViewController.swift index 3ed22f17..480c29bf 100644 --- a/NEContactUIKit/NEKitContactUI/Classes/Views/ContactsSelectedViewController.swift +++ b/NEContactUIKit/NEKitContactUI/Classes/Views/ContactsSelectedViewController.swift @@ -15,6 +15,9 @@ open class ContactsSelectedViewController: ContactBaseViewController { public var limit = 10 // max select count + // 单聊中对方的userId + public var userId: String? + var selectArray = [ContactInfo]() let selectDic = [String: ContactInfo]() lazy var collection: UICollectionView = { @@ -42,7 +45,7 @@ open class ContactsSelectedViewController: ContactBaseViewController { super.viewDidLoad() // Do any additional setup after loading the view. - title = "选择" + title = localizable("select") setupUI() setupNavRightItem() @@ -108,7 +111,7 @@ open class ContactsSelectedViewController: ContactBaseViewController { let rightItem = UIBarButtonItem(customView: sureBtn) navigationItem.rightBarButtonItem = rightItem sureBtn.addTarget(self, action: #selector(sureClick(_:)), for: .touchUpInside) - sureBtn.setTitle("确定", for: .normal) + sureBtn.setTitle(localizable("alert_sure"), for: .normal) sureBtn.setTitleColor(UIColor(hexString: "337EFF"), for: .normal) sureBtn.titleLabel?.font = UIFont.systemFont(ofSize: 16.0) sureBtn.contentHorizontalAlignment = .right @@ -129,7 +132,7 @@ open class ContactsSelectedViewController: ContactBaseViewController { extension ContactsSelectedViewController { @objc func sureClick(_ sender: UIButton) { if selectArray.count <= 0 { - view.makeToast("请选择联系人") + view.makeToast(localizable("select_contact")) return } if let completion = callBack { @@ -153,6 +156,9 @@ extension ContactsSelectedViewController { } } + if let uid = userId { + accids.append(uid) + } let nameString = names.joined(separator: "、") print("name string : ", nameString) Router.shared.use( @@ -293,7 +299,7 @@ extension ContactsSelectedViewController: UICollectionViewDelegate, UICollection if selectArray.count > 0 { sureBtn.setTitle("确定(\(selectArray.count))", for: .normal) } else { - sureBtn.setTitle("确定", for: .normal) + sureBtn.setTitle(localizable("alert_sure"), for: .normal) } } } diff --git a/NEContactUIKit/NEKitContactUI/Classes/Views/ContactsViewController.swift b/NEContactUIKit/NEKitContactUI/Classes/Views/ContactsViewController.swift index e282266f..51f083d4 100644 --- a/NEContactUIKit/NEKitContactUI/Classes/Views/ContactsViewController.swift +++ b/NEContactUIKit/NEKitContactUI/Classes/Views/ContactsViewController.swift @@ -19,19 +19,19 @@ open class ContactsViewController: UIViewController, UITableViewDelegate, UITabl var tableView = UITableView(frame: .zero, style: .grouped) var viewModel = ContactViewModel(contactHeaders: [ ContactHeadItem( - name: "验证消息", + name: localizable("validation_message"), imageName: "valid", router: ValidationMessageRouter, color: UIColor(hexString: "#60CFA7") ), ContactHeadItem( - name: "黑名单", + name: localizable("blacklist"), imageName: "blackName", router: ContactBlackListRouter, color: UIColor(hexString: "#53C3F3") ), ContactHeadItem( - name: "我的群聊", + name: localizable("mine_groupchat"), imageName: "group", router: ContactGroupRouter, color: UIColor(hexString: "#BE65D9") diff --git a/NEContactUIKit/NEKitContactUI/Classes/Views/FindFriendViewController.swift b/NEContactUIKit/NEKitContactUI/Classes/Views/FindFriendViewController.swift index 606dbbb1..a8d02210 100644 --- a/NEContactUIKit/NEKitContactUI/Classes/Views/FindFriendViewController.swift +++ b/NEContactUIKit/NEKitContactUI/Classes/Views/FindFriendViewController.swift @@ -22,7 +22,7 @@ public class FindFriendViewController: ContactBaseViewController, UITextFieldDel lazy var userLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false - label.text = "该用户不存在" + label.text = localizable("user_not_exist") label.textColor = .ne_emptyTitleColor label.font = UIFont.systemFont(ofSize: 14.0) return label @@ -30,7 +30,7 @@ public class FindFriendViewController: ContactBaseViewController, UITextFieldDel override public func viewDidLoad() { super.viewDidLoad() - title = "添加好友" + title = localizable("add_friend") setupUI() } diff --git a/NEConversationUIKit/NEKitConversationUI.podspec b/NEConversationUIKit/NEKitConversationUI.podspec index e3a18ef4..c3bf309d 100644 --- a/NEConversationUIKit/NEKitConversationUI.podspec +++ b/NEConversationUIKit/NEKitConversationUI.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'NEKitConversationUI' - s.version = '9.2.7' + s.version = '9.2.8' s.summary = 'Netease XKit' # This description is used to generate tags and improve search results. @@ -22,7 +22,6 @@ TODO: Add long description of the pod here. DESC s.homepage = 'http://netease.im' - # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' s.license = { :'type' => 'Copyright', :'text' => ' Copyright 2022 Netease '} s.author = 'yunxin engineering department' s.source = { :git => 'ssh://git@g.hz.netease.com:22222/yunxin-app/xkit-ios.git', :tag => s.version.to_s } @@ -33,11 +32,11 @@ TODO: Add long description of the pod here. s.source_files = 'NEKitConversationUI/Classes/**/*' s.resource = 'NEKitConversationUI/Assets/**/*' # s.public_header_files = 'Pod/Classes/**/*.h' - s.pod_target_xcconfig = { - 'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES' - } + 'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES' + } s.user_target_xcconfig = { 'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES' } + s.dependency 'NECommonUIKit' s.dependency 'NEConversationKit' diff --git a/NEConversationUIKit/NEKitConversationUI/Assets/en.lproj/Localizable.strings b/NEConversationUIKit/NEKitConversationUI/Assets/en.lproj/Localizable.strings index eec66e03..c0dd9841 100644 --- a/NEConversationUIKit/NEKitConversationUI/Assets/en.lproj/Localizable.strings +++ b/NEConversationUIKit/NEKitConversationUI/Assets/en.lproj/Localizable.strings @@ -4,12 +4,31 @@ // found in the LICENSE file. //MAKR:common - -"add_friend"="添加好友"; -"create_discussion_group"="创建讨论组"; -"create_senior_group"="创建高级群"; - - +"add_friend"="Add Contacts"; +"create_discussion_group"="Create Temp Group"; +"create_senior_group"="Create Group"; +"cancel_stickTop"="Cancel topping"; +"stickTop"="Sticky on Top"; +"delete"="Cancel"; +"friend"="Contact"; +"discussion_group"="Temp Group"; +"senior_group"="Group"; +"search"="Search"; +"search_keyword"="Enter the key words"; +"user_not_exist"="Not Exist"; +"hm"="HH:mm"; +"mdhm"="MM.dd HH:mm"; +"ymdhm"="yyyy.MM.dd HH:mm"; +"voice"="[aduio]"; +"picture"="[picture]"; +"video"="[Video]"; +"location"="[Loaction]"; +"notification"="[Tip]"; +"file"="[File]"; +"internet_phone"="[Audio_Chat]"; +"video_chat"="[Video_Chat]"; +"unknown"="[Unknown Message]"; +"appName"="CommsEase IM"; diff --git a/NEConversationUIKit/NEKitConversationUI/Assets/zh-Hans.lproj/Localizable.strings b/NEConversationUIKit/NEKitConversationUI/Assets/zh-Hans.lproj/Localizable.strings index e2514171..bf369508 100644 --- a/NEConversationUIKit/NEKitConversationUI/Assets/zh-Hans.lproj/Localizable.strings +++ b/NEConversationUIKit/NEKitConversationUI/Assets/zh-Hans.lproj/Localizable.strings @@ -7,3 +7,25 @@ "add_friend"="添加好友"; "create_discussion_group"="创建讨论组"; "create_senior_group"="创建高级群"; +"cancel_stickTop"="取消置顶"; +"stickTop"="置顶"; +"delete"="删除"; +"friend"="好友"; +"discussion_group"="讨论组"; +"senior_group"="高级群"; +"search"="搜索"; +"search_keyword"="请输入你要搜索的关键字"; +"user_not_exist"="该用户不存在"; +"hm"="HH:mm"; +"mdhm"="MM月dd日 HH:mm"; +"ymdhm"="yyyy年MM月dd日 HH:mm"; +"voice"="[语音]"; +"picture"="[图片]"; +"video"="[视频]"; +"location"="[位置]"; +"notification"="[通知]"; +"file"="[文件]"; +"internet_phone"="[网络通话]"; +"video_chat"="[视频聊天]"; +"unknown"="[未知消息]"; +"appName"="云信IM"; diff --git a/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/Controller/ConversationListViewController.swift b/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/Controller/ConversationListViewController.swift index 63fe16ec..d17ca1c8 100644 --- a/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/Controller/ConversationListViewController.swift +++ b/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/Controller/ConversationListViewController.swift @@ -197,7 +197,7 @@ extension ConversationListViewController: UITableViewDelegate, UITableViewDataSo } let deleteAction = UITableViewRowAction(style: .destructive, - title: "删除") { action, indexPath in + title: localizable("delete")) { action, indexPath in weakSelf?.viewModel.deleteRecentSession(recentSession: recentSession) weakSelf?.didDeleteConversationCell( @@ -224,7 +224,8 @@ extension ConversationListViewController: UITableViewDelegate, UITableViewDataSo // 置顶和取消置顶 let isTop = viewModel.stickTopInfos[session] != nil let topAction = UITableViewRowAction(style: .destructive, - title: isTop ? "取消置顶" : "置顶") { action, indexPath in + title: isTop ? localizable("cancel_stickTop") : + localizable("stickTop")) { action, indexPath in if let recentSesstion = conversationModel?.recentSession { weakSelf?.onTopRecentAtIndexPath( rencent: recentSesstion, diff --git a/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/Controller/ConversationSearchController.swift b/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/Controller/ConversationSearchController.swift index 64a5ceb5..08769a95 100644 --- a/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/Controller/ConversationSearchController.swift +++ b/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/Controller/ConversationSearchController.swift @@ -53,11 +53,16 @@ open class SearchSessionHeaderView: UITableViewHeaderFooterView { }() } -public class ConversationSearchController: NEBaseViewController,UITableViewDelegate, UITableViewDataSource { +public class ConversationSearchController: NEBaseViewController, UITableViewDelegate, + UITableViewDataSource { private var viewModel = ConversationSearchViewModel() private let tag = "ConversationSearchController" private var searchStr = "" - private let headTitleArr = ["好友", "讨论组", "高级群"] + private let headTitleArr = [ + localizable("friend"), + localizable("discussion_group"), + localizable("senior_group"), + ] override public func viewDidLoad() { super.viewDidLoad() @@ -101,7 +106,7 @@ public class ConversationSearchController: NEBaseViewController,UITableViewDeleg } func initialConfig() { - title = "搜索" + title = localizable("search") } // MARK: private method @@ -170,7 +175,7 @@ public class ConversationSearchController: NEBaseViewController,UITableViewDeleg textField.contentMode = .center textField.leftView = leftImageView textField.leftViewMode = .always - textField.placeholder = localizable("搜索") + textField.placeholder = localizable("search") textField.font = UIFont.systemFont(ofSize: 14) textField.textColor = UIColor.ne_greyText textField.translatesAutoresizingMaskIntoConstraints = false @@ -179,112 +184,116 @@ public class ConversationSearchController: NEBaseViewController,UITableViewDeleg textField.clearButtonMode = .whileEditing textField.returnKeyType = .search textField.addTarget(self, action: #selector(searchTextFieldChange), for: .editingChanged) - textField.placeholder = "请输入你要搜索的关键字" + textField.placeholder = localizable("search_keyword") return textField }() private lazy var emptyView: NEEmptyDataView = { - let view = NEEmptyDataView(imageName: "user_empty", content: "该用户不存在", frame: CGRect.zero) + let view = NEEmptyDataView( + imageName: "user_empty", + content: localizable("user_not_exist"), + frame: CGRect.zero + ) view.translatesAutoresizingMaskIntoConstraints = false view.isHidden = true return view }() - - //MARK: UITableViewDelegate, UITableViewDataSource - public func numberOfSections(in tableView: UITableView) -> Int { - return 3 - } - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if let friend = viewModel.searchResult?.friend, section == 0 { - return friend.count - } else if let contactGroup = viewModel.searchResult?.contactGroup, section == 1 { - return contactGroup.count - } else if let seniorGroup = viewModel.searchResult?.seniorGroup, section == 2 { - return seniorGroup.count - } else { - return 0 - } + // MARK: UITableViewDelegate, UITableViewDataSource + + public func numberOfSections(in tableView: UITableView) -> Int { + 3 + } + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if let friend = viewModel.searchResult?.friend, section == 0 { + return friend.count + } else if let contactGroup = viewModel.searchResult?.contactGroup, section == 1 { + return contactGroup.count + } else if let seniorGroup = viewModel.searchResult?.seniorGroup, section == 2 { + return seniorGroup.count + } else { + return 0 } + } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell( - withIdentifier: "\(NSStringFromClass(ConversationSearchCell.self))", - for: indexPath - ) as! ConversationSearchCell - if indexPath.section == 0 { - cell.searchModel = viewModel.searchResult?.friend[indexPath.row] - } else if indexPath.section == 1 { - cell.searchModel = viewModel.searchResult?.contactGroup[indexPath.row] - } else { - cell.searchModel = viewModel.searchResult?.seniorGroup[indexPath.row] - } - cell.searchText = searchStr - return cell + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell( + withIdentifier: "\(NSStringFromClass(ConversationSearchCell.self))", + for: indexPath + ) as! ConversationSearchCell + if indexPath.section == 0 { + cell.searchModel = viewModel.searchResult?.friend[indexPath.row] + } else if indexPath.section == 1 { + cell.searchModel = viewModel.searchResult?.contactGroup[indexPath.row] + } else { + cell.searchModel = viewModel.searchResult?.seniorGroup[indexPath.row] } + cell.searchText = searchStr + return cell + } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if indexPath.section == 0 { - let searchModel = viewModel.searchResult?.friend[indexPath.row] - if let userId = searchModel?.userInfo?.userId { - let session = NIMSession(userId, type: .P2P) - Router.shared.use( - PushP2pChatVCRouter, - parameters: ["nav": navigationController as Any, "session": session as Any], - closure: nil - ) - } + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.section == 0 { + let searchModel = viewModel.searchResult?.friend[indexPath.row] + if let userId = searchModel?.userInfo?.userId { + let session = NIMSession(userId, type: .P2P) + Router.shared.use( + PushP2pChatVCRouter, + parameters: ["nav": navigationController as Any, "session": session as Any], + closure: nil + ) + } - } else if indexPath.section == 1 { - let searchModel = viewModel.searchResult?.contactGroup[indexPath.row] - if let teamId = searchModel?.teamInfo?.teamId { - let session = NIMSession(teamId, type: .team) - Router.shared.use( - PushTeamChatVCRouter, - parameters: ["nav": navigationController as Any, "session": session as Any], - closure: nil - ) - } - } else { - let searchModel = viewModel.searchResult?.seniorGroup[indexPath.row] - if let teamId = searchModel?.teamInfo?.teamId { - let session = NIMSession(teamId, type: .team) - Router.shared.use( - PushTeamChatVCRouter, - parameters: ["nav": navigationController as Any, "session": session as Any], - closure: nil - ) - } + } else if indexPath.section == 1 { + let searchModel = viewModel.searchResult?.contactGroup[indexPath.row] + if let teamId = searchModel?.teamInfo?.teamId { + let session = NIMSession(teamId, type: .team) + Router.shared.use( + PushTeamChatVCRouter, + parameters: ["nav": navigationController as Any, "session": session as Any], + closure: nil + ) + } + } else { + let searchModel = viewModel.searchResult?.seniorGroup[indexPath.row] + if let teamId = searchModel?.teamInfo?.teamId { + let session = NIMSession(teamId, type: .team) + Router.shared.use( + PushTeamChatVCRouter, + parameters: ["nav": navigationController as Any, "session": session as Any], + closure: nil + ) } } + } - public func tableView(_ tableView: UITableView, - viewForHeaderInSection section: Int) -> UIView? { - let sectionView = tableView - .dequeueReusableHeaderFooterView( - withIdentifier: "\(NSStringFromClass(SearchSessionHeaderView.self))" - ) as! SearchSessionHeaderView - sectionView.setUpTitle(title: headTitleArr[section]) - sectionView.backgroundView = UIView() - return sectionView - } + public func tableView(_ tableView: UITableView, + viewForHeaderInSection section: Int) -> UIView? { + let sectionView = tableView + .dequeueReusableHeaderFooterView( + withIdentifier: "\(NSStringFromClass(SearchSessionHeaderView.self))" + ) as! SearchSessionHeaderView + sectionView.setUpTitle(title: headTitleArr[section]) + sectionView.backgroundView = UIView() + return sectionView + } - public func tableView(_ tableView: UITableView, - heightForHeaderInSection section: Int) -> CGFloat { - if let friend = viewModel.searchResult?.friend, friend.count > 0, section == 0 { - return 30 - - } else if let contactGroup = viewModel.searchResult?.contactGroup, contactGroup.count > 0, - section == 1 { - return 30 - } else if let seniorGroup = viewModel.searchResult?.seniorGroup, seniorGroup.count > 0, - section == 2 { - return 30 - } else { - return 0 - } + public func tableView(_ tableView: UITableView, + heightForHeaderInSection section: Int) -> CGFloat { + if let friend = viewModel.searchResult?.friend, friend.count > 0, section == 0 { + return 30 + + } else if let contactGroup = viewModel.searchResult?.contactGroup, contactGroup.count > 0, + section == 1 { + return 30 + } else if let seniorGroup = viewModel.searchResult?.seniorGroup, seniorGroup.count > 0, + section == 2 { + return 30 + } else { + return 0 } + } } - diff --git a/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/ConversationRouter/ConversationRouter.swift b/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/ConversationRouter/ConversationRouter.swift index 31a1b4f2..6dcfd0c0 100644 --- a/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/ConversationRouter/ConversationRouter.swift +++ b/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/ConversationRouter/ConversationRouter.swift @@ -5,7 +5,9 @@ import Foundation -public enum ConversationRouter { +@objcMembers +public class ConversationRouter:NSObject { + public static func register() { Router.shared.register(SearchContactPageRouter) { param in let nav = param["nav"] as? UINavigationController @@ -19,4 +21,5 @@ public enum ConversationRouter { nav?.pushViewController(conversation, animated: true) } } + } diff --git a/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/View/ConversationListCell.swift b/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/View/ConversationListCell.swift index 41ed31be..1cc1bcb7 100644 --- a/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/View/ConversationListCell.swift +++ b/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/View/ConversationListCell.swift @@ -184,16 +184,16 @@ open class ConversationListCell: UITableViewCell { let fmt = DateFormatter() if targetDate.isToday() { - fmt.dateFormat = "HH:mm" + fmt.dateFormat = localizable("hm") return fmt.string(from: targetDate) } else { if targetDate.isThisYear() { - fmt.dateFormat = "MM月dd日 HH:mm" + fmt.dateFormat = localizable("mdhm") return fmt.string(from: targetDate) } else { - fmt.dateFormat = "yyyy年MM月dd日 HH:mm" + fmt.dateFormat = localizable("ymdhm") return fmt.string(from: targetDate) } } diff --git a/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/View/ConversationNavView.swift b/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/View/ConversationNavView.swift index e2492e0a..5af9686d 100644 --- a/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/View/ConversationNavView.swift +++ b/NEConversationUIKit/NEKitConversationUI/Classes/Conversation/View/ConversationNavView.swift @@ -81,7 +81,7 @@ class ConversationNavView: UIView { private lazy var brandBtn: UIButton = { let button = UIButton() - button.setTitle(localizable("云信IM"), for: .normal) + button.setTitle(localizable("appName"), for: .normal) button.setImage(UIImage.ne_imageNamed(name: "brand_yunxin"), for: .normal) button.layoutButtonImage(style: .left, space: 12) button.translatesAutoresizingMaskIntoConstraints = false diff --git a/NEConversationUIKit/NEKitConversationUI/Classes/Util/NEMessageUtil.swift b/NEConversationUIKit/NEKitConversationUI/Classes/Util/NEMessageUtil.swift index 0562b989..ece6b873 100644 --- a/NEConversationUIKit/NEKitConversationUI/Classes/Util/NEMessageUtil.swift +++ b/NEConversationUIKit/NEKitConversationUI/Classes/Util/NEMessageUtil.swift @@ -17,26 +17,27 @@ public class NEMessageUtil { text = messageText } case .audio: - text = "[语音]" + text = localizable("voice") case .image: - text = "[图片]" + text = localizable("picture") case .video: - text = "[视频]" + text = localizable("video") case .location: - text = "[位置]" + text = localizable("location") case .notification: - text = "[通知]" + text = localizable("notification") case .file: - text = "[文件]" + text = localizable("file") case .tip: if let messageText = message.text { text = messageText } case .rtcCallRecord: let record = message.messageObject as? NIMRtcCallRecordObject - text = (record?.callType == .audio) ? "[网络通话]" : "[视频聊天]" + text = (record?.callType == .audio) ? localizable("internet_phone") : + localizable("video_chat") default: - text = "[未知消息]" + text = localizable("unknown") } return text diff --git a/NEQChatUIKit/NEKitQChatUI.podspec b/NEQChatUIKit/NEKitQChatUI.podspec index 78f14b2e..00c61aac 100644 --- a/NEQChatUIKit/NEKitQChatUI.podspec +++ b/NEQChatUIKit/NEKitQChatUI.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'NEKitQChatUI' - s.version = '9.2.7' + s.version = '9.2.8' s.summary = 'Netease XKit' # This description is used to generate tags and improve search results. @@ -35,7 +35,6 @@ TODO: Add long description of the pod here. s.resource_bundles = { 'NEKitQChatUI' => ['NEKitQChatUI/Assets/*.png'] } - s.resource = 'NEKitQChatUI/Assets/**/*' s.dependency 'NECommonUIKit' s.dependency 'NEQChatKit' diff --git a/NEQChatUIKit/NEKitQChatUI/Assets/en.lproj/Localizable.strings b/NEQChatUIKit/NEKitQChatUI/Assets/en.lproj/Localizable.strings index 3f93e4d3..844efdbd 100644 --- a/NEQChatUIKit/NEKitQChatUI/Assets/en.lproj/Localizable.strings +++ b/NEQChatUIKit/NEKitQChatUI/Assets/en.lproj/Localizable.strings @@ -4,192 +4,163 @@ // found in the LICENSE file. //MAKR:common -"ok"="ok"; -"exceed"="超出"; -"person"="人"; -"limit"="限制"; +"ok"="Ok"; +"exceed"="Exceed"; +"person"="Member"; +"limit"="Limit"; + //MARK: Server -"qchat_setting"="服务器设置"; -"close"="关闭"; -"qchat_delete_server"="删除服务器"; -"qchat_leave_server"="退出服务器"; -"qchat_permisssion"="权限"; -"qchat_server_theme"="服务器主题"; -"qchat_server_name"="服务器名称"; -"qchat_id_group"="身份组"; -"qchat_group_default_permission"="所有服务器成员默认权限"; -"qchat_sort"="排序"; +"qchat_setting"="Server Settings"; +"qchat_delete_server"="Delete Server"; +"qchat_leave_server"="Exit Server"; +"qchat_permisssion"="Permission"; +"qchat_server_theme"="Server Theme"; +"qchat_server_name"="Server Name"; +"qchat_id_group"="Role"; +"qchat_group_default_permission"="Default Permission for everyone"; +"qchat_sort"="sort"; +"qchat_create_new_id_group"="Create new Role"; +"qchat_group_name"="Role Name"; +"qchat_manager_member"="Role management"; +"qchat_edit"="Edit"; +"qchat_save"="Save"; +"qchat_nickname"="Nick Name"; +"qcaht_edit_nickname"="Edit Nick Name"; +"qchat_kick_out"="Kick out"; +"qchat_prohibit"="Prohibit"; +"qchat_manager_server"="Manage Server"; +"qchat_manager_channel"="Manage all cannel info"; +"qchat_manager_role"="Manage Role"; +"qchat_send_message"="Send Message"; +"qchat_delete_message"="Delete other's message in Server"; +"qchat_recall_message"="Recall other's message in Server"; +"qchat_at_any"="@ anyone"; +"qchat_at_all"="@ everyone"; +"qchat_modify_own_server"="Edit my info in Server"; +"qchat_modify_other_server"="Edit other's Server info"; +"qchat_invite_member"="Invite Permission"; +"qchat_kickout_member"="Kick out"; +"qchat_manager_channel_list"="Manage channel members"; +"qchat_common_permission"="Common Permission"; +"qchat_message_permission"="Message Permission"; +"qchat_member_permission"="Member Permission"; +"qchat_member"="Member"; +"qchat_id_group_sort"="Role Sort"; +"qchat_sure"="Ok"; +"qchat_cancel"="cancel"; +"qchat_tip"="Tip"; +"qchat_select"="Select"; +"qchat_please_input_topic"="Please enter the theme"; +"qchat_not_empty_servername"="Please Name the server"; +"qchat_not_empty_header_url"="Please upload Avator"; +"qchat_not_empty_select_memeber"="Please Select member"; +"qchat_please_input_role_name"="Please set the role name"; +"qchat_sure_delete_user"="wether to delete this member"; +"qchat_delete_success"="Delete successfully"; +"qchat_add_success"="Add successfully"; +"qchat_add_Server"="create Server"; +"qchat_mine_add"="create"; +"qchat_join_otherServer"="Join other Server"; -"qchat_create_new_id_group"="创建新身份组"; -"qchat_add_member"="添加成员"; -"qchat_group_name"="身份组名称"; -"qchat_manager_member"="成员管理"; -"qchat_edit"="编辑"; -"qchat_save"="保存"; -"qchat_nickname"="昵称"; -"qcaht_edit_nickname"="编辑昵称"; -"qchat_kick_out"="踢除"; -"qchat_prohibit"="封禁"; -"qchat_manager_server"="管理服务器"; -"qchat_manager_channel"="管理所有频道属性"; -"qchat_manager_role"="管理角色"; -"qchat_send_message"="发送消息"; -"qchat_delete_message"="所有频道中删除他人消息"; -"qchat_recall_message"="所有频道撤回他人消息"; -"qchat_at_any"="@任意人的权限"; -"qchat_at_all"="@所有人的权限"; -"qchat_modify_own_server"="修改自己服务器成员信息"; -"qchat_modify_other_server"="修改他人服务器成员信息"; -"qchat_invite_member"="邀请他人进入server权限"; -"qchat_kickout_member"="踢人"; -"qchat_manager_channel_list"="管理频道名单"; -"qchat_common_permission"="通用权限"; -"qchat_message_permission"="消息权限"; -"qchat_member_permission"="成员权限"; -"qchat_member"="成员"; -"qchat_id_group_sort"="身份组排序"; -"qchat_sure"="确定"; -"qchat_cancel"="取消"; -"qchat_tip"="提示"; -"qchat_select"="选择"; -"qchat_please_input_topic"="请输入主题"; -"qchat_not_empty_servername"="服务器名不能为空"; -"qchat_not_empty_header_url"="请先上传头像"; -"qchat_not_empty_select_memeber"="请选择用户"; -"qchat_please_input_role_name"="请输入身份组名称"; -"qchat_sure_delete_user"="确定删除当前用户?"; -"qchat_delete_success"="删除成功"; -"qchat_add_success"="添加成功"; -"qchat_add_Server"="创建服务器"; -"qchat_mine_add"="自己创建"; -"qchat_join_otherServer"="加入别人服务器"; +//MARK: Channel +"create_channel"="Create New Channel"; +"delete_channel"="Delete Channel"; +"confirm_delete_channel"="Sure to Remove ?"; + +"create"="Creat"; +"cancel"="Cancel"; +"channel_name"="Channel Name"; +"channel_topic"="Channel Theme"; +"channel_type"="Channel Type"; +"input_channel_name"="Enter Name"; +"input_channel_topic"="Enter Theme"; +"public"="Public"; +"private"="Private"; +"online"="Online"; +"offline"="Offline"; +"first_channel"="Channel 1"; +"second_channel"="channel 2"; +"more"="More"; +"has_no_role"="No more role"; +"send_to"="Send to"; +"image_is_nil"= "No picture"; -////MARK: Channel -//"create_channel"="Create Channel"; -//"delete_channel"="Delete Channel"; -//"confirm_delete_channel"="Would you like to delete "; -// -//"create"="Create"; -//"cancel"="Cancel"; -//"channel_name"="Channel Name"; -//"channel_topic"="Channel Topic"; -//"channel_type"="Channel Type"; -//"input_channel_name"="Please enter name of channel"; -//"input_channel_topic"="Please enter topic of channel"; -//"public"="Public"; -//"private"="Private"; -//"online"="Online"; -//"offline"="Offline"; -//"first_channel"="频道1"; -//"second_channel"="频道2"; -// -//"more"="more"; -// -// -////MARK:toast -//"update_channel_suscess"="Update Channel Suscess"; -//"delete_channel_suscess"="Delete Channel Suscess"; -// -// -////MARK: channel_setting -//"channel_setting"="Setting"; -//"save_setting"="Save"; -//"close"="Close"; -//"authority"="Authority"; -//"authority_setting"="Authority setting"; -//"list"="List"; -//"white_list"="White List"; -//"black_list"="Black List"; -//"channel_member"="Members"; -//"delete_channel"="Delete Channel"; -//"authority_setting"="Authority Setting"; -// -//"add_group"="Add Authority Group"; -//"add_member"="Add Member"; -//"member_permission_setting"="Member Permission Setting"; -//"group_permission_setting"="Permission Setting"; -//"finish"="Finished"; -//"removeMember"="Remove Member"; -//MARK: Channel -"create_channel"="创建新频道"; -"delete_channel"="删除频道"; -"confirm_delete_channel"="确认要移除"; -"create"="创建"; -"cancel"="取消"; -"channel_name"="频道名称"; -"channel_topic"="频道主题"; -"channel_type"="频道类型"; -"input_channel_name"="请输入名称"; -"input_channel_topic"="请输入主题"; -"public"="公开"; -"private"="私密"; -"online"="在线"; -"offline"="离线"; -"first_channel"="频道1"; -"second_channel"="频道2"; -"more"="更多"; -"has_no_role"="无身份组可添加"; -"send_to"="发送给"; -"image_is_nil"= "图片为空"; -"text_is_nil"="内容为空"; //MARK:toast -"update_channel_suscess"="保存成功"; -"delete_channel_suscess"="删除成功"; -"open_soon"="暂未开放"; - +"update_channel_suscess"="Save Successfully"; +"delete_channel_suscess"="Delete Successfully"; +"open_soon"="Not Open"; //MARK: channel_setting -"channel_setting"="频道设置"; -"save"="保存"; -"close"="关闭"; -"authority"="权限"; -"authority_setting"="权限设置"; -"list"="名单"; -"white_list"="白名单"; -"black_list"="黑名单"; -"channel_member"="频道成员"; -"delete_channel"="删除频道"; -"authority_setting"="权限设置"; +"channel_setting"="Channel Setting"; +"save"="Save"; +"close"="Close"; +"authority"="Permission"; +"authority_setting"="Permission Setting"; +"list"="List"; +"white_list"="whitelist"; +"black_list"="blacklist"; +"channel_member"="Channel Member"; +"delete_channel"="Delete Channel"; + +"add_group"="Add Role"; +"add_member"="Add member"; +"member_permission_setting"="Member Permission Setting"; +"finish"="Finish"; +"removeMember"="Remove Memebr"; +"removeRole"="Remove Role"; -"add_group"="添加身份组"; -"add_member"="添加成员"; -"member_permission_setting"="成员权限设置"; -"group_permission_setting"="权限设置"; -"finish"="完成"; -"removeMember"="移除成员"; -"removeRole"="移除身份组"; //MARK:身份组权限设置 "auth1"="ManageServer"; -"auth2"="管理频道属性"; -"auth3"="管理频道权限"; -"auth4"="发送消息"; +"auth2"="Manage Channel Info"; +"auth3"="Manage Channel Permission"; +"auth4"="Send Message"; "auth5"="ModifySelfInfo"; "auth6"="InviteToServer"; "auth7"="KickOthersInServer"; "auth8"="ModifyOthersInfoInServer"; -"auth9"="频道中撤回他人消息"; -"auth10"="频道中删除他人消息"; +"auth9"="Recall other's Message"; +"auth10"="Delete other's Message"; "auth11"="RemindOther"; "auth12"="RemindAll"; -"auth13"="管理频道名单"; +"auth13"="Manage Channel Memeber"; + +//supplement +"delete"="delete"; +"share_thoughts"="Share Thoughts"; +"noMember_add"="No member to add"; +"search_serverId"="Search ServerID"; +"no_serverId"="Not Found"; +"know"="ok"; +"upload_headImage"="Upload Avator"; +"enter_serverName"="Please enter server name"; +"request_sended"="invit sent"; +"message_channel"="Message Channel"; +"server_nochannel"="No Channel"; +"applied"="Applied"; +"join"="Join"; +"modify_nickname"="Click to edit nick name"; +"sure_exit_server"="wether to exit server?"; +"sure_delete_server"="wether to delete server?"; +"serverId_notbe_empty"="Cannot be empty "; +"enter_name"="Enter Name"; +"kick_currentMember"="Wether to kick out this member?"; +"sure_delete"=" Wether to delete?"; +"current_identity"="Current Identity Group"; +"hm"="HH:mm"; +"mdhm"="MM.dd HH:mm"; +"ymdhm"="yyyy.MM.dd HH:mm"; //MARK:error toast "param_error"="Parameter error"; -"no_Permession"="暂无权限在该频道发言"; - - - - - - - - - - - - - - +"no_Permession"="No permission to send message"; +"channelName_cannot_be_empty"="Cannot be empty"; +"network_error"="No internet"; +"cant_join"="Cannot join?"; +"blocked_from_server_cant_join"="You are blocked by this server and cannot join"; +"serverName_limit"="Cannot exceed 50 characters"; +"add_favorite_service"="No Server, Let's join or create one"; +"nickName_not_empty"="Cannot be empty "; +"accid_not_empty"="Cannot be empty"; diff --git a/NEQChatUIKit/NEKitQChatUI/Assets/zh-Hans.lproj/Localizable.strings b/NEQChatUIKit/NEKitQChatUI/Assets/zh-Hans.lproj/Localizable.strings index 76f672f2..05b5e4a9 100644 --- a/NEQChatUIKit/NEKitQChatUI/Assets/zh-Hans.lproj/Localizable.strings +++ b/NEQChatUIKit/NEKitQChatUI/Assets/zh-Hans.lproj/Localizable.strings @@ -21,7 +21,6 @@ "qchat_group_default_permission"="所有服务器成员默认权限"; "qchat_sort"="排序"; "qchat_create_new_id_group"="创建新身份组"; -"qchat_add_member"="添加成员"; "qchat_group_name"="身份组名称"; "qchat_manager_member"="成员管理"; "qchat_edit"="编辑"; @@ -86,7 +85,7 @@ "has_no_role"="无身份组可添加"; "send_to"="发送给"; "image_is_nil"= "图片为空"; -"text_is_nil"="内容为空"; + //MARK:toast @@ -105,12 +104,12 @@ "black_list"="黑名单"; "channel_member"="频道成员"; "delete_channel"="删除频道"; -"authority_setting"="权限设置"; + "add_group"="添加身份组"; "add_member"="添加成员"; "member_permission_setting"="成员权限设置"; -"group_permission_setting"="权限设置"; + "finish"="完成"; "removeMember"="移除成员"; "removeRole"="移除身份组"; @@ -130,7 +129,40 @@ "auth12"="RemindAll"; "auth13"="管理频道名单"; +//supplement +"delete"="删除"; +"share_thoughts"="分享心得"; +"noMember_add"="无成员可添加"; +"search_serverId"="搜索服务器ID"; +"no_serverId"="暂无你要的服务器ID"; +"know"="知道了"; +"upload_headImage"="上传头像"; +"enter_serverName"=" 请输入服务器名称"; +"request_sended"="请求已发送"; +"message_channel"="消息频道"; +"server_nochannel"="该服务器下暂无频道"; +"applied"="已申请"; +"join"="加入"; +"modify_nickname"="请点击编辑后修改昵称"; +"sure_exit_server"="确定退出当前服务器?"; +"sure_delete_server"="确定删除当前服务器?"; +"serverId_notbe_empty"="服务器id不能为空"; +"enter_name"="输入名称"; +"kick_currentMember"="确定踢出当前成员?"; +"sure_delete"="确定删除"; +"current_identity"="当前身份组"; +"hm"="HH:mm"; +"mdhm"="MM月dd日 HH:mm"; +"ymdhm"="yyyy年MM月dd日 HH:mm"; + //MARK:error toast "param_error"="Parameter error"; "no_Permession"="暂无权限在该频道发言"; - +"channelName_cannot_be_empty"="频道名称不能为空"; +"network_error"="当前网络错误"; +"cant_join"="无法加入?"; +"blocked_from_server_cant_join"="你被该服务器封禁,无法加入。"; +"serverName_limit"="服务器命名不超过50个字符"; +"add_favorite_service"="暂无服务器,\n赶紧去添加心仪的服务器吧"; +"nickName_not_empty"="昵称不能为空"; +"accid_not_empty"="accid 不能为空"; diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Base/BaseView/QChatImageTextCell.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Base/BaseView/QChatImageTextCell.swift index bbd832b0..914d115d 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Base/BaseView/QChatImageTextCell.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Base/BaseView/QChatImageTextCell.swift @@ -67,7 +67,6 @@ class QChatImageTextCell: QChatStateCell { name.textColor = .white name.textAlignment = .center name.font = UIFont.systemFont(ofSize: 14.0) - name.text = "果冻" return name }() diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Base/BaseView/QChatTextCell.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Base/BaseView/QChatTextCell.swift index 731a2465..baf04109 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Base/BaseView/QChatTextCell.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Base/BaseView/QChatTextCell.swift @@ -31,7 +31,7 @@ class QChatTextCell: QChatStateCell { titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor), titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), ]) - titleLabel.text = "删除" + titleLabel.text = localizable("delete") detailLabel.font = UIFont.systemFont(ofSize: 16) detailLabel.translatesAutoresizingMaskIntoConstraints = false diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/View/ChannelHeaderView.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/View/ChannelHeaderView.swift index c0125df6..a9a39a57 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/View/ChannelHeaderView.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/View/ChannelHeaderView.swift @@ -27,7 +27,7 @@ class ChannelHeaderView: UIView { titleLabel.font = .systemFont(ofSize: 18) titleLabel.textColor = .ne_darkText - titleLabel.text = "频道1" + titleLabel.text = localizable("first_channel") titleLabel.translatesAutoresizingMaskIntoConstraints = false addSubview(titleLabel) NSLayoutConstraint.activate([ @@ -39,7 +39,7 @@ class ChannelHeaderView: UIView { detailLabel.textColor = .ne_greyText detailLabel.font = .systemFont(ofSize: 14) - detailLabel.text = "分享心得" + detailLabel.text = localizable("share_thoughts") detailLabel.translatesAutoresizingMaskIntoConstraints = false addSubview(detailLabel) NSLayoutConstraint.activate([ diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/View/QChatPermissionSettingCell.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/View/QChatPermissionSettingCell.swift index 976fb3f5..c8b9a430 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/View/QChatPermissionSettingCell.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/View/QChatPermissionSettingCell.swift @@ -45,7 +45,7 @@ class QChatPermissionSettingCell: QChatCornerCell { titleLabel.font = UIFont.systemFont(ofSize: 16) titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.textColor = .ne_darkText - titleLabel.text = "删除" + titleLabel.text = localizable("delete") contentView.addSubview(titleLabel) NSLayoutConstraint.activate([ titleLabel.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 35), diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/View/QChatTextEditCell.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/View/QChatTextEditCell.swift index 588a376b..341ae5a0 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/View/QChatTextEditCell.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/View/QChatTextEditCell.swift @@ -5,13 +5,12 @@ import UIKit -@objc protocol QChatTextEditCellDelegate:AnyObject { +@objc protocol QChatTextEditCellDelegate: AnyObject { // @objc optional func textFieldDidChangeSelection(cell: QChatTextEditCell, // _ textField: UITextField) @objc optional func textDidChange(_ textField: UITextField) } - class QChatTextEditCell: QChatCornerCell, UITextFieldDelegate { var limit: Int? var canEdit = true diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatAddMemberVC.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatAddMemberVC.swift index d1a23325..1191cc8a 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatAddMemberVC.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatAddMemberVC.swift @@ -219,7 +219,7 @@ public class QChatAddMemberVC: QChatSearchVC { private lazy var emptyView: EmptyDataView = { let view = EmptyDataView( imageName: "memberPlaceholder", - content: "无成员可添加", + content: localizable("noMember_add"), frame: CGRect( x: 0, y: 60, diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatChannelMembersVC.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatChannelMembersVC.swift index 4f250781..e7c96d1a 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatChannelMembersVC.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatChannelMembersVC.swift @@ -168,7 +168,7 @@ public class QChatChannelMembersVC: QChatTableViewController, QChatMemberInfoVie func didClickUserHeader(_ accid: String?) { if let uid = accid { - if IMKitLoginManager.instance.isMySelf(uid) { + if IMKitEngine.instance.isMySelf(uid) { Router.shared.use( MeSettingRouter, parameters: ["nav": navigationController as Any], diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatChannelViewController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatChannelViewController.swift index fcea1056..cbac7902 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatChannelViewController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatChannelViewController.swift @@ -143,12 +143,11 @@ public class QChatChannelViewController: QChatTableViewController, QChatTextEdit // MARK: event @objc func createChannel() { - - guard let name = viewModel?.name,name.count > 0 else { - self.showToast("频道名称不能为空") - return - } - + guard let name = viewModel?.name, name.count > 0 else { + showToast(localizable("channelName_cannot_be_empty")) + return + } + if !isCreatedChannel { isCreatedChannel = true viewModel?.createChannel { error, channel in diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatWhiteBlackListVC.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatWhiteBlackListVC.swift index b9824d1c..20842dce 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatWhiteBlackListVC.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Channel/ViewController/QChatWhiteBlackListVC.swift @@ -73,7 +73,7 @@ public class QChatWhiteBlackListVC: QChatTableViewController, QChatMemberSelectC ) as! QChatTextCell cell.backgroundColor = .white cell.rightStyle = .indicate - cell.titleLabel.text = localizable("qchat_add_member") + cell.titleLabel.text = localizable("add_member") return cell } else { let cell = tableView.dequeueReusableCell( @@ -87,7 +87,7 @@ public class QChatWhiteBlackListVC: QChatTableViewController, QChatMemberSelectC // if CoreKitEngine.instance.imAccid == member?.accid, self.type == .white { // cell.rightStyle = .none // } - if IMKitLoginManager.instance.imAccid == member?.accid, type == .white { + if IMKitEngine.instance.imAccid == member?.accid, type == .white { cell.rightStyle = .none } return cell @@ -103,7 +103,6 @@ public class QChatWhiteBlackListVC: QChatTableViewController, QChatMemberSelectC memberSelect.delegate = self memberSelect.completion = { [weak self] datas in // 选中成员 - print("选中成员:datas:\(datas)") if datas.count > 0 { var seletedMembers = [ServerMemeber]() for data in datas { @@ -135,7 +134,7 @@ public class QChatWhiteBlackListVC: QChatTableViewController, QChatMemberSelectC // return // } - if IMKitLoginManager.instance.imAccid == member.accid, type == .white { + if IMKitEngine.instance.imAccid == member.accid, type == .white { return } diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Chat/Controller/QChatViewController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Chat/Controller/QChatViewController.swift index 1a1284a1..0d11dd7f 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Chat/Controller/QChatViewController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Chat/Controller/QChatViewController.swift @@ -13,7 +13,7 @@ import NEKitCommon import NEKitCore public class QChatViewController: NEBaseViewController, UINavigationControllerDelegate, - QChatInputViewDelegate, QChatViewModelDelegate,UITableViewDataSource, UITableViewDelegate { + QChatInputViewDelegate, QChatViewModelDelegate, UITableViewDataSource, UITableViewDelegate { private let tag = "QChatViewController" private var viewmodel: QChatViewModel? private var inputViewBottomConstraint: NSLayoutConstraint? @@ -62,169 +62,169 @@ public class QChatViewController: NEBaseViewController, UINavigationControllerDe deinit { NELog.infoLog(className(), desc: "✅ QChatViewController release") } - - func commonUI() { - title = viewmodel?.channel?.name - addLeftAction(UIImage.ne_imageNamed(name: "server_menu"), #selector(enterServerVC), self) - addRightAction( - UIImage.ne_imageNamed(name: "channel_member"), - #selector(enterChannelMemberVC), - self - ) - view.addSubview(tableView) + func commonUI() { + title = viewmodel?.channel?.name + addLeftAction(UIImage.ne_imageNamed(name: "server_menu"), #selector(enterServerVC), self) + addRightAction( + UIImage.ne_imageNamed(name: "channel_member"), + #selector(enterChannelMemberVC), + self + ) + + view.addSubview(tableView) + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint( + equalTo: view.topAnchor, + constant: kNavigationHeight + KStatusBarHeight + ), + tableView.leftAnchor.constraint(equalTo: view.leftAnchor), + tableView.rightAnchor.constraint(equalTo: view.rightAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -130), + ]) + + tableView.register( + QChatBaseTableViewCell.self, + forCellReuseIdentifier: "\(QChatBaseTableViewCell.self)" + ) + tableView.register( + QChatTextTableViewCell.self, + forCellReuseIdentifier: "\(QChatTextTableViewCell.self)" + ) + tableView.register( + QChatImageTableViewCell.self, + forCellReuseIdentifier: "\(QChatImageTableViewCell.self)" + ) + tableView.register( + QChatTimeTableViewCell.self, + forCellReuseIdentifier: "\(QChatTimeTableViewCell.self)" + ) + + // IQKeyboardManager.shared.enable = false + IQKeyboardManager.shared.keyboardDistanceFromTextField = 60 + IQKeyboardManager.shared.enable = true + IQKeyboardManager.shared.enableAutoToolbar = false + let inputView = QChatInputView() + var tip = localizable("send_to") + if let cName = viewmodel?.channel?.name { + tip = tip + cName + } + inputView.textField.placeholder = tip + + inputView.translatesAutoresizingMaskIntoConstraints = false + inputView.delegate = self + view.addSubview(inputView) + if #available(iOS 11.0, *) { + self.inputViewBottomConstraint = inputView.bottomAnchor + .constraint(equalTo: self.view.bottomAnchor) + // self.inputViewBottomConstraint = inputView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor) NSLayoutConstraint.activate([ - tableView.topAnchor.constraint( - equalTo: view.topAnchor, - constant: kNavigationHeight + KStatusBarHeight - ), - tableView.leftAnchor.constraint(equalTo: view.leftAnchor), - tableView.rightAnchor.constraint(equalTo: view.rightAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -130), + inputView.leftAnchor.constraint(equalTo: self.view.leftAnchor), + inputView.rightAnchor.constraint(equalTo: self.view.rightAnchor), + inputView.heightAnchor.constraint(equalToConstant: 100), ]) + } else { + // Fallback on earlier versions + inputViewBottomConstraint = inputView.bottomAnchor + .constraint(equalTo: view.bottomAnchor) + NSLayoutConstraint.activate([ + inputView.leftAnchor.constraint(equalTo: view.leftAnchor), + inputView.rightAnchor.constraint(equalTo: view.rightAnchor), + inputView.heightAnchor.constraint(equalToConstant: 100), + ]) + } + inputViewBottomConstraint?.isActive = true - tableView.register( - QChatBaseTableViewCell.self, - forCellReuseIdentifier: "\(QChatBaseTableViewCell.self)" - ) - tableView.register( - QChatTextTableViewCell.self, - forCellReuseIdentifier: "\(QChatTextTableViewCell.self)" - ) - tableView.register( - QChatImageTableViewCell.self, - forCellReuseIdentifier: "\(QChatImageTableViewCell.self)" - ) - tableView.register( - QChatTimeTableViewCell.self, - forCellReuseIdentifier: "\(QChatTimeTableViewCell.self)" - ) - - // IQKeyboardManager.shared.enable = false - IQKeyboardManager.shared.keyboardDistanceFromTextField = 60 - IQKeyboardManager.shared.enable = true - IQKeyboardManager.shared.enableAutoToolbar = false - let inputView = QChatInputView() - var tip = localizable("send_to") - if let cName = viewmodel?.channel?.name { - tip = tip + cName - } - inputView.textField.placeholder = tip - - inputView.translatesAutoresizingMaskIntoConstraints = false - inputView.delegate = self - view.addSubview(inputView) - if #available(iOS 11.0, *) { - self.inputViewBottomConstraint = inputView.bottomAnchor - .constraint(equalTo: self.view.bottomAnchor) - // self.inputViewBottomConstraint = inputView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor) - NSLayoutConstraint.activate([ - inputView.leftAnchor.constraint(equalTo: self.view.leftAnchor), - inputView.rightAnchor.constraint(equalTo: self.view.rightAnchor), - inputView.heightAnchor.constraint(equalToConstant: 100), - ]) + weak var weakSelf = self + NEChatDetectNetworkTool.shareInstance.netWorkReachability { status in + if status == .notReachable, let networkView = weakSelf?.brokenNetworkView { + weakSelf?.view.addSubview(networkView) } else { - // Fallback on earlier versions - inputViewBottomConstraint = inputView.bottomAnchor - .constraint(equalTo: view.bottomAnchor) - NSLayoutConstraint.activate([ - inputView.leftAnchor.constraint(equalTo: view.leftAnchor), - inputView.rightAnchor.constraint(equalTo: view.rightAnchor), - inputView.heightAnchor.constraint(equalToConstant: 100), - ]) - } - inputViewBottomConstraint?.isActive = true - - weak var weakSelf = self - NEChatDetectNetworkTool.shareInstance.netWorkReachability { status in - if status == .notReachable, let networkView = weakSelf?.brokenNetworkView { - weakSelf?.view.addSubview(networkView) - } else { - weakSelf?.brokenNetworkView.removeFromSuperview() - } + weakSelf?.brokenNetworkView.removeFromSuperview() } } + } // MARK: event - @objc func enterChannelMemberVC() { - let memberVC = QChatChannelMembersVC() - memberVC.channel = viewmodel?.channel - navigationController?.pushViewController(memberVC, animated: true) - } + @objc func enterChannelMemberVC() { + let memberVC = QChatChannelMembersVC() + memberVC.channel = viewmodel?.channel + navigationController?.pushViewController(memberVC, animated: true) + } - @objc func enterServerVC() { - navigationController?.popViewController(animated: true) - } + @objc func enterServerVC() { + navigationController?.popViewController(animated: true) + } - func loadData() { - weak var weakSelf = self - viewmodel?.getMessageHistory { error, messages in - - if let err = error { - NELog.errorLog(self.tag, desc: "❌getMessageHistory error, error:\(err)") - } else { - if let tempArray = weakSelf?.viewmodel?.messages, tempArray.count > 0 { - weakSelf?.tableView.reloadData() - weakSelf?.tableView.scrollToRow( - at: IndexPath(row: tempArray.count - 1, section: 0), - at: .bottom, - animated: false - ) - if let time = messages?.first?.message?.timestamp { - weakSelf?.viewmodel?.markMessageRead(time: time) - } + func loadData() { + weak var weakSelf = self + viewmodel?.getMessageHistory { error, messages in + + if let err = error { + NELog.errorLog(self.tag, desc: "❌getMessageHistory error, error:\(err)") + } else { + if let tempArray = weakSelf?.viewmodel?.messages, tempArray.count > 0 { + weakSelf?.tableView.reloadData() + weakSelf?.tableView.scrollToRow( + at: IndexPath(row: tempArray.count - 1, section: 0), + at: .bottom, + animated: false + ) + if let time = messages?.first?.message?.timestamp { + weakSelf?.viewmodel?.markMessageRead(time: time) } } } } + } - @objc func loadMoreData() { - weak var weakSelf = self - viewmodel?.getMoreMessageHistory { error, messageFrames in - weakSelf?.tableView.reloadData() - weakSelf?.tableView.mj_header?.endRefreshing() - } + @objc func loadMoreData() { + weak var weakSelf = self + viewmodel?.getMoreMessageHistory { error, messageFrames in + weakSelf?.tableView.reloadData() + weakSelf?.tableView.mj_header?.endRefreshing() } + } - func addObseve() { - NotificationCenter.default.addObserver( - self, - selector: #selector(onUpdateChannel), - name: NotificationName.updateChannel, - object: nil - ) - NotificationCenter.default.addObserver( - self, - selector: #selector(onDeleteChannel), - name: NotificationName.deleteChannel, - object: nil - ) - // NotificationCenter.default.addObserver(self, - // selector: #selector(keyBoardWillShow(_ :)), - // name: UIResponder.keyboardWillShowNotification, - // object: nil) - // - // NotificationCenter.default.addObserver(self, - // selector: #selector(keyBoardWillHide(_ :)), - // name: UIResponder.keyboardWillHideNotification, - // object: nil) - } + func addObseve() { + NotificationCenter.default.addObserver( + self, + selector: #selector(onUpdateChannel), + name: NotificationName.updateChannel, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(onDeleteChannel), + name: NotificationName.deleteChannel, + object: nil + ) + // NotificationCenter.default.addObserver(self, + // selector: #selector(keyBoardWillShow(_ :)), + // name: UIResponder.keyboardWillShowNotification, + // object: nil) + // + // NotificationCenter.default.addObserver(self, + // selector: #selector(keyBoardWillHide(_ :)), + // name: UIResponder.keyboardWillHideNotification, + // object: nil) + } - @objc func onUpdateChannel(noti: Notification) { - // enter ChatVC - guard let channel = noti.object as? ChatChannel else { - return - } - viewmodel?.channel = channel - title = viewmodel?.channel?.name + @objc func onUpdateChannel(noti: Notification) { + // enter ChatVC + guard let channel = noti.object as? ChatChannel else { + return } + viewmodel?.channel = channel + title = viewmodel?.channel?.name + } - @objc func onDeleteChannel(noti: Notification) { - navigationController?.popToRootViewController(animated: true) - } + @objc func onDeleteChannel(noti: Notification) { + navigationController?.popToRootViewController(animated: true) + } - // MARK: 键盘通知相关操作 + // MARK: 键盘通知相关操作 // @objc func keyBoardWillShow(_ notification:Notification) { // let keyboardRect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue @@ -241,144 +241,143 @@ public class QChatViewController: NEBaseViewController, UINavigationControllerDe // }) // } - // MARK: QChatInputViewDelegate - - func sendText(text: String?) { - NELog.infoLog(tag, desc: "sendText:\(text ?? "")") - guard let content = text, content.count > 0 else { - showToast(localizable("text_is_nil")) + // MARK: QChatInputViewDelegate - return - } - viewmodel?.sendTextMessage(text: content) { [weak self] error in - if error != nil { - self?.view.makeToast(error?.localizedDescription) - } else {} - } + func sendText(text: String?) { + NELog.infoLog(tag, desc: "sendText:\(text ?? "")") + guard let content = text, content.count > 0 else { + return + } + viewmodel?.sendTextMessage(text: content) { [weak self] error in + if error != nil { + self?.view.makeToast(error?.localizedDescription) + } else {} } + } - func willSelectItem(button: UIButton, index: Int) { - if index == 2 { - showBottomAlert(self, false) - } else { - showToast(localizable("open_soon")) - } + func willSelectItem(button: UIButton, index: Int) { + if index == 2 { + showBottomAlert(self, false) + } else { + showToast(localizable("open_soon")) } + } // MARK: UIImagePickerControllerDelegate - public func imagePickerController(_ picker: UIImagePickerController, - didFinishPickingMediaWithInfo info: [UIImagePickerController - .InfoKey: Any]) { - picker.dismiss(animated: true, completion: nil) - guard let image = info[.originalImage] as? UIImage else { - showToast(localizable("image_is_nil")) - return - } - // 发送消息 - viewmodel?.sendImageMessage(image: image) { [weak self] error in - if error != nil { - self?.view.makeToast(error?.localizedDescription) - } + public func imagePickerController(_ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController + .InfoKey: Any]) { + picker.dismiss(animated: true, completion: nil) + guard let image = info[.originalImage] as? UIImage else { + showToast(localizable("image_is_nil")) + return + } + // 发送消息 + viewmodel?.sendImageMessage(image: image) { [weak self] error in + if error != nil { + self?.view.makeToast(error?.localizedDescription) } } + } // MARK: QChatViewModelDelegate - public func onRecvMessages(_ messages: [NIMQChatMessage]) { - tableView.reloadData() - if let messageCount = viewmodel?.messages.count, messageCount > 1 { - tableView.scrollToRow( - at: IndexPath(row: messageCount - 1, section: 0), - at: .bottom, - animated: false - ) - if let time = viewmodel?.messages.last?.message?.timestamp { - viewmodel?.markMessageRead(time: time) - } + public func onRecvMessages(_ messages: [NIMQChatMessage]) { + tableView.reloadData() + if let messageCount = viewmodel?.messages.count, messageCount > 1 { + tableView.scrollToRow( + at: IndexPath(row: messageCount - 1, section: 0), + at: .bottom, + animated: false + ) + if let time = viewmodel?.messages.last?.message?.timestamp { + viewmodel?.markMessageRead(time: time) } } + } - public func send(_ message: NIMQChatMessage, progress: Float) {} + public func send(_ message: NIMQChatMessage, progress: Float) {} - public func send(_ message: NIMQChatMessage, didCompleteWithError error: Error?) { - if let e = error as NSError? { - if e.code == 403 { - showAlert(message: localizable("no_Permession")) {} - } - } - tableView.reloadData() - if let messageCount = viewmodel?.messages.count, messageCount > 1 { - tableView.scrollToRow( - at: IndexPath(row: messageCount - 1, section: 0), - at: .bottom, - animated: false - ) + public func send(_ message: NIMQChatMessage, didCompleteWithError error: Error?) { + if let e = error as NSError? { + if e.code == 403 { + showAlert(message: localizable("no_Permession")) {} } } - - public func willSend(_ message: NIMQChatMessage) { - tableView.reloadData() - if let messageCount = viewmodel?.messages.count, messageCount > 1 { - tableView.scrollToRow( - at: IndexPath(row: messageCount - 1, section: 0), - at: .bottom, - animated: false - ) - } + tableView.reloadData() + if let messageCount = viewmodel?.messages.count, messageCount > 1 { + tableView.scrollToRow( + at: IndexPath(row: messageCount - 1, section: 0), + at: .bottom, + animated: false + ) } - - //MARK: UITableViewDataSource, UITableViewDelegate - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - viewmodel?.messages.count ?? 0 + } + + public func willSend(_ message: NIMQChatMessage) { + tableView.reloadData() + if let messageCount = viewmodel?.messages.count, messageCount > 1 { + tableView.scrollToRow( + at: IndexPath(row: messageCount - 1, section: 0), + at: .bottom, + animated: false + ) } + } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let messageFrame = viewmodel?.messages[indexPath.row] - var reuseIdentifier = "\(QChatBaseTableViewCell.self)" + // MARK: UITableViewDataSource, UITableViewDelegate - guard let msgFrame = messageFrame else { - return tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) - } + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + viewmodel?.messages.count ?? 0 + } - if msgFrame.showTime { - reuseIdentifier = "\(QChatTimeTableViewCell.self)" - } else { - // 根据cell类型区分identify - switch msgFrame.message?.messageType { - case .text: - reuseIdentifier = "\(QChatTextTableViewCell.self)" - case .image: - reuseIdentifier = "\(QChatImageTableViewCell.self)" - default: - reuseIdentifier = "\(QChatBaseTableViewCell.self)" - } - } + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let messageFrame = viewmodel?.messages[indexPath.row] + var reuseIdentifier = "\(QChatBaseTableViewCell.self)" - if msgFrame.showTime { - let cell = tableView.dequeueReusableCell( - withIdentifier: reuseIdentifier, - for: indexPath - ) as! QChatTimeTableViewCell - cell.messageFrame = messageFrame - return cell - } else { - let cell = tableView.dequeueReusableCell( - withIdentifier: reuseIdentifier, - for: indexPath - ) as! QChatBaseTableViewCell - cell.messageFrame = messageFrame - cell.delegate = self - return cell + guard let msgFrame = messageFrame else { + return tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) + } + + if msgFrame.showTime { + reuseIdentifier = "\(QChatTimeTableViewCell.self)" + } else { + // 根据cell类型区分identify + switch msgFrame.message?.messageType { + case .text: + reuseIdentifier = "\(QChatTextTableViewCell.self)" + case .image: + reuseIdentifier = "\(QChatImageTableViewCell.self)" + default: + reuseIdentifier = "\(QChatBaseTableViewCell.self)" } } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - let messageFrame = viewmodel?.messages[indexPath.row] - return messageFrame?.cellHeight ?? 0 + if msgFrame.showTime { + let cell = tableView.dequeueReusableCell( + withIdentifier: reuseIdentifier, + for: indexPath + ) as! QChatTimeTableViewCell + cell.messageFrame = messageFrame + return cell + } else { + let cell = tableView.dequeueReusableCell( + withIdentifier: reuseIdentifier, + for: indexPath + ) as! QChatBaseTableViewCell + cell.messageFrame = messageFrame + cell.delegate = self + return cell } + } + + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + let messageFrame = viewmodel?.messages[indexPath.row] + return messageFrame?.cellHeight ?? 0 + } } // MARK: =============== QChatBaseCellDelegate ================ @@ -392,7 +391,7 @@ extension QChatViewController: QChatBaseCellDelegate { } func didClickHeader(_ message: NIMQChatMessage) { - if IMKitLoginManager.instance.isMySelf(message.from) == true { + if IMKitEngine.instance.isMySelf(message.from) == true { Router.shared.use( MeSettingRouter, parameters: ["nav": navigationController as Any], @@ -420,4 +419,3 @@ extension QChatViewController: QChatBaseCellDelegate { } else if messgae.messageType == .audio {} } } - diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Chat/ViewModel/QChatViewModel.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Chat/ViewModel/QChatViewModel.swift index 846fd9e0..825ee0ea 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Chat/ViewModel/QChatViewModel.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Chat/ViewModel/QChatViewModel.swift @@ -34,7 +34,7 @@ public class QChatViewModel: NSObject, NIMQChatMessageManagerDelegate { if let cid = channel?.channelId, let sid = channel?.serverId { let message = NIMQChatMessage() message.text = text - message.from = IMKitLoginManager.instance.imAccid + message.from = IMKitEngine.instance.imAccid QChatSystemMessageProvider.shared.sendMessage( message: message, session: NIMSession(forQChat: Int64(cid), qchatServerId: Int64(sid)) @@ -49,7 +49,7 @@ public class QChatViewModel: NSObject, NIMQChatMessageManagerDelegate { if let cid = channel?.channelId, let sid = channel?.serverId { let message = NIMQChatMessage() message.messageObject = NIMImageObject(image: image) - message.from = IMKitLoginManager.instance.imAccid + message.from = IMKitEngine.instance.imAccid QChatSystemMessageProvider.shared.sendMessage( message: message, session: NIMSession(forQChat: Int64(cid), qchatServerId: Int64(sid)) diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Extension/QChatStringExtension.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Extension/QChatStringExtension.swift index f8d0a42e..e2801047 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Extension/QChatStringExtension.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Extension/QChatStringExtension.swift @@ -18,17 +18,17 @@ extension String { static func stringFromDate(date: Date) -> String { let fmt = DateFormatter() if Calendar.current.isDateInToday(date) { - fmt.dateFormat = "HH:mm" + fmt.dateFormat = localizable("hm") } else { if let firstDayYear = firstDayInYear() { let dur = date.timeIntervalSince(firstDayYear) if dur > 0 { - fmt.dateFormat = "MM月dd日 HH:mm" + fmt.dateFormat = localizable("mdhm") } else { - fmt.dateFormat = "yyyy年MM月dd日 HH:mm" + fmt.dateFormat = localizable("ymdhm") } } else { - fmt.dateFormat = "yyyy年MM月dd日 HH:mm" + fmt.dateFormat = localizable("ymdhm") } } return fmt.string(from: date) diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/CreateServerViewController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/CreateServerViewController.swift index 99b4ad8c..dc0b4968 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/CreateServerViewController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/CreateServerViewController.swift @@ -5,7 +5,8 @@ import UIKit -public class CreateServerViewController: NEBaseViewController,UITableViewDelegate, UITableViewDataSource { +public class CreateServerViewController: NEBaseViewController, UITableViewDelegate, + UITableViewDataSource { public var serverViewModel = CreateServerViewModel() override public func viewDidLoad() { @@ -46,44 +47,40 @@ public class CreateServerViewController: NEBaseViewController,UITableViewDelegat tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 0.1)) return tableView }() - - @objc func closeAction(sender: UIButton) { - navigationController?.dismiss(animated: true, completion: nil) - } - - - //MARK: UITableViewDelegate, UITableViewDataSource - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - serverViewModel.dataArray.count - } - - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell( - withIdentifier: "\(NSStringFromClass(NECreateServerCell.self))", - for: indexPath - ) as! NECreateServerCell - let model = serverViewModel.dataArray[indexPath.row] - cell.model = model - return cell - } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if indexPath.row == 0 { - let mineCreateCtrl = MineCreateServerController() - navigationController?.pushViewController(mineCreateCtrl, animated: true) - } else if indexPath.row == 1 { - let otherCtrl = JoinOtherServiceController() - navigationController?.pushViewController(otherCtrl, animated: true) - } - } + @objc func closeAction(sender: UIButton) { + navigationController?.dismiss(animated: true, completion: nil) + } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - 76 - } -} + // MARK: UITableViewDelegate, UITableViewDataSource + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + serverViewModel.dataArray.count + } + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell( + withIdentifier: "\(NSStringFromClass(NECreateServerCell.self))", + for: indexPath + ) as! NECreateServerCell + let model = serverViewModel.dataArray[indexPath.row] + cell.model = model + return cell + } + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.row == 0 { + let mineCreateCtrl = MineCreateServerController() + navigationController?.pushViewController(mineCreateCtrl, animated: true) + } else if indexPath.row == 1 { + let otherCtrl = JoinOtherServiceController() + navigationController?.pushViewController(otherCtrl, animated: true) + } + } + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + 76 + } +} diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/JoinOtherServiceController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/JoinOtherServiceController.swift index 9fd1c86d..5438b0d6 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/JoinOtherServiceController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/JoinOtherServiceController.swift @@ -9,7 +9,8 @@ import NEKitCoreIM import NEKitCommon import IQKeyboardManagerSwift -public class JoinOtherServiceController: NEBaseViewController,UITableViewDelegate, UITableViewDataSource { +public class JoinOtherServiceController: NEBaseViewController, UITableViewDelegate, + UITableViewDataSource { private let tag = "JoinOtherServiceController" public var serversArray = [QChatServer]() public var serverViewModel = CreateServerViewModel() @@ -65,7 +66,7 @@ public class JoinOtherServiceController: NEBaseViewController,UITableViewDelegat textField.contentMode = .center textField.leftView = leftImageView textField.leftViewMode = .always - textField.placeholder = localizable("搜索服务器ID") + textField.placeholder = localizable("search_serverId") textField.font = DefaultTextFont(14) textField.textColor = TextNormalColor textField.translatesAutoresizingMaskIntoConstraints = false @@ -95,99 +96,99 @@ public class JoinOtherServiceController: NEBaseViewController,UITableViewDelegat private lazy var emptyView: EmptyDataView = { let view = EmptyDataView( imageName: "searchServer_noMoreData", - content: "暂无你要的服务器ID", + content: localizable("no_serverId"), frame: tableView.bounds ) return view }() - - @objc func searchTextFieldChange(textfield: SearchTextField) { - // 选择高亮文本在进行搜索 - // let textRange = textfield.markedTextRange - // if textRange == nil || ((textRange?.isEmpty) == nil) { - // print("111") - // } - - if !NEChatDetectNetworkTool.shareInstance.isNetworkRecahability() { - showToast("当前网络错误") - return - } - guard let content = textfield.text else { - return - } - // 空字符串判断 - if content.isBlank { - emptyView.removeFromSuperview() - return - } + @objc func searchTextFieldChange(textfield: SearchTextField) { + // 选择高亮文本在进行搜索 + // let textRange = textfield.markedTextRange + // if textRange == nil || ((textRange?.isEmpty) == nil) { + // print("111") + // } + + if !NEChatDetectNetworkTool.shareInstance.isNetworkRecahability() { + showToast(localizable("network_error")) + return + } - let param = QChatGetServersParam(serverIds: [NSNumber(value: UInt64(content)!)]) - serverViewModel.getServers(parameter: param) { error, serversArray in - if error == nil { - self.serversArray = serversArray?.servers ?? Array() - if self.serversArray.isEmpty { - self.tableView.addSubview(self.emptyView) - return - } else { - self.emptyView.removeFromSuperview() - } - self.tableView.reloadData() + guard let content = textfield.text else { + return + } + // 空字符串判断 + if content.isBlank { + emptyView.removeFromSuperview() + return + } + + let param = QChatGetServersParam(serverIds: [NSNumber(value: UInt64(content)!)]) + serverViewModel.getServers(parameter: param) { error, serversArray in + if error == nil { + self.serversArray = serversArray?.servers ?? Array() + if self.serversArray.isEmpty { + self.tableView.addSubview(self.emptyView) + return } else { - NELog.errorLog(self.tag, desc: "❌getServers failed,error = \(error!)") + self.emptyView.removeFromSuperview() } + self.tableView.reloadData() + } else { + NELog.errorLog(self.tag, desc: "❌getServers failed,error = \(error!)") } } - - // MARK: UITableViewDelegate UITableViewDataSource - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - serversArray.count - } + } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell( - withIdentifier: "\(NSStringFromClass(NESearchServerCell.self))", - for: indexPath - ) as! NESearchServerCell - cell.serverModel = serversArray[indexPath.row] - weak var weakSelf = self - cell.joinServerCallBack = { - let successView = - InviteMemberView(frame: CGRect(x: (kScreenWidth - 176) / 2, y: KStatusBarHeight, - width: 176, height: 55)) - successView.showSuccessView() - } - return cell + // MARK: UITableViewDelegate UITableViewDataSource + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + serversArray.count + } + + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell( + withIdentifier: "\(NSStringFromClass(NESearchServerCell.self))", + for: indexPath + ) as! NESearchServerCell + cell.serverModel = serversArray[indexPath.row] + weak var weakSelf = self + cell.joinServerCallBack = { + let successView = + InviteMemberView(frame: CGRect(x: (kScreenWidth - 176) / 2, y: KStatusBarHeight, + width: 176, height: 55)) + successView.showSuccessView() } + return cell + } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let serverId = serversArray[indexPath.row].serverId else { return } - let param = QChatGetChannelsByPageParam(timeTag: 0, serverId: serverId) - weak var weakSelf = self - channelViewModel.getChannelsByPage(parameter: param) { error, result in - if error == nil { - guard let dataArray = result?.channels else { return } - let chatVC = QChatViewController(channel: dataArray.first) - weakSelf?.navigationController?.pushViewController(chatVC, animated: true) - } else { - print("getChannelsByPage failed,error = \(error!)") - } + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let serverId = serversArray[indexPath.row].serverId else { return } + let param = QChatGetChannelsByPageParam(timeTag: 0, serverId: serverId) + weak var weakSelf = self + channelViewModel.getChannelsByPage(parameter: param) { error, result in + if error == nil { + guard let dataArray = result?.channels else { return } + let chatVC = QChatViewController(channel: dataArray.first) + weakSelf?.navigationController?.pushViewController(chatVC, animated: true) + } else { + print("getChannelsByPage failed,error = \(error!)") } } + } } - // MARK: private Method extension JoinOtherServiceController { func showAlert() { let alertCtrl = UIAlertController( - title: localizable("无法加入?"), - message: localizable("你被该服务器封禁,无法加入。"), + title: localizable("cant_join"), + message: localizable("blocked_from_server_cant_join"), preferredStyle: .alert ) - let okAction = UIAlertAction(title: localizable("知道了"), style: .default, handler: nil) + let okAction = UIAlertAction(title: localizable("know"), style: .default, handler: nil) alertCtrl.addAction(okAction) present(alertCtrl, animated: true, completion: nil) } diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/MemberListViewController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/MemberListViewController.swift index d5ccf03d..d403f482 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/MemberListViewController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/MemberListViewController.swift @@ -6,7 +6,8 @@ import UIKit import NEKitCoreIM import NEKitCore -public class MemberListViewController: NEBaseViewController,UITableViewDelegate, UITableViewDataSource { +public class MemberListViewController: NEBaseViewController, UITableViewDelegate, + UITableViewDataSource { public var serverViewModel = CreateServerViewModel() public var memberViewModel = MemberListViewModel() @@ -37,7 +38,7 @@ public class MemberListViewController: NEBaseViewController,UITableViewDelegate, } func initializeConfig() { - title = "成员" + title = localizable("qchat_member") addRightAction(UIImage.ne_imageNamed(name: "sign_add"), #selector(addMemberClick), self) } @@ -67,78 +68,76 @@ public class MemberListViewController: NEBaseViewController,UITableViewDelegate, tableView.estimatedRowHeight = 125 return tableView }() - - //MAKR: UITableViewDelegate, UITableViewDataSource - @objc func addMemberClick(sender: UIButton) { - Router.shared.register(ContactSelectedUsersRouter) { [weak self] param in - print("param\(param)") - if let userIds = param["accids"] as? [String] { - print("userIds:\(userIds)") - guard let serverId = self?.serverId else { return } - self?.serverViewModel - .inviteMembersToServer(serverId: serverId, accids: userIds) { error in - if error == nil { - self?.requestData() - } + + // MAKR: UITableViewDelegate, UITableViewDataSource + @objc func addMemberClick(sender: UIButton) { + Router.shared.register(ContactSelectedUsersRouter) { [weak self] param in + print("param\(param)") + if let userIds = param["accids"] as? [String] { + print("userIds:\(userIds)") + guard let serverId = self?.serverId else { return } + self?.serverViewModel + .inviteMembersToServer(serverId: serverId, accids: userIds) { error in + if error == nil { + self?.requestData() } - } + } } + } - Router.shared - .use(ContactUserSelectRouter, - parameters: ["nav": navigationController]) { obj, routerState, str in - print("obj:\(obj) routerState:\(routerState) str:\(str)") - } + Router.shared + .use(ContactUserSelectRouter, + parameters: ["nav": navigationController]) { obj, routerState, str in + print("obj:\(obj) routerState:\(routerState) str:\(str)") + } - // FIXME: router - // let contactCtrl = ContactsSelectedViewController() - // self.navigationController?.pushViewController(contactCtrl, animated: true) - // weak var weakSelf = self - // - // contactCtrl.callBack = {(selectMemberarray)->Void in - // - // guard let serverId = weakSelf?.serverId else { return } - // var accidArray = [String]() - // selectMemberarray.forEach { memberInfo in - // accidArray.append(memberInfo.user?.userId ?? "") - // } - // weakSelf?.serverViewModel.inviteMembersToServer(serverId: serverId, accids: accidArray) { error in - // if error == nil{ - // weakSelf?.requestData() - // } - // } - // } - } + // FIXME: router + // let contactCtrl = ContactsSelectedViewController() + // self.navigationController?.pushViewController(contactCtrl, animated: true) + // weak var weakSelf = self + // + // contactCtrl.callBack = {(selectMemberarray)->Void in + // + // guard let serverId = weakSelf?.serverId else { return } + // var accidArray = [String]() + // selectMemberarray.forEach { memberInfo in + // accidArray.append(memberInfo.user?.userId ?? "") + // } + // weakSelf?.serverViewModel.inviteMembersToServer(serverId: serverId, accids: accidArray) { error in + // if error == nil{ + // weakSelf?.requestData() + // } + // } + // } + } - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - dataArray?.count ?? 0 - } + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + dataArray?.count ?? 0 + } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell( - withIdentifier: "\(NSStringFromClass(NEGroupIdentityMemberCell.self))", - for: indexPath - ) as! NEGroupIdentityMemberCell - cell.memberModel = dataArray?[indexPath.row] - return cell - } + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell( + withIdentifier: "\(NSStringFromClass(NEGroupIdentityMemberCell.self))", + for: indexPath + ) as! NEGroupIdentityMemberCell + cell.memberModel = dataArray?[indexPath.row] + return cell + } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - // let user = viewModel.limitUsers[indexPath.row] - if let member = dataArray?[indexPath.row] { - let editMember = QChatEditMemberViewController() - editMember.deleteCompletion = { - self.requestData() - } - editMember.changeCompletion = { - self.requestData() - } - let user = UserInfo(member) - editMember.user = user - navigationController?.pushViewController(editMember, animated: true) + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + // let user = viewModel.limitUsers[indexPath.row] + if let member = dataArray?[indexPath.row] { + let editMember = QChatEditMemberViewController() + editMember.deleteCompletion = { + self.requestData() + } + editMember.changeCompletion = { + self.requestData() } + let user = UserInfo(member) + editMember.user = user + navigationController?.pushViewController(editMember, animated: true) } + } } - - diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/MineCreateServerController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/MineCreateServerController.swift index c1169132..b774e8b1 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/MineCreateServerController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/MineCreateServerController.swift @@ -9,7 +9,8 @@ import NEKitCoreIM import NIMSDK import NEKitCommon -public class MineCreateServerController: NEBaseViewController, UINavigationControllerDelegate,UITextFieldDelegate { +public class MineCreateServerController: NEBaseViewController, UINavigationControllerDelegate, + UITextFieldDelegate { private let tag = "MineCreateServerController" public var serverViewModel = CreateServerViewModel() var headImageUrl: String? @@ -108,7 +109,7 @@ public class MineCreateServerController: NEBaseViewController, UINavigationContr private lazy var uploadDesLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false - label.text = localizable("上传头像") + label.text = localizable("upload_headImage") label.textColor = HexRGB(0x656A72) label.font = DefaultTextFont(12) return label @@ -117,7 +118,7 @@ public class MineCreateServerController: NEBaseViewController, UINavigationContr private lazy var textField: UITextField = { let textField = UITextField() textField.setValue(NSNumber(value: 10), forKey: "paddingLeft") - textField.placeholder = localizable(" 请输入服务器名称") + textField.placeholder = localizable("enter_serverName") textField.font = DefaultTextFont(16) textField.textColor = TextNormalColor textField.translatesAutoresizingMaskIntoConstraints = false @@ -140,102 +141,101 @@ public class MineCreateServerController: NEBaseViewController, UINavigationContr button.addTarget(self, action: #selector(createServerBtnClick), for: .touchUpInside) return button }() - - - @objc func createServerBtnClick(sender: UIButton) { - guard let serverName = textField.text, serverName.count > 0 else { return } - if NEChatDetectNetworkTool.shareInstance.isNetworkRecahability() { - sender.isEnabled = false - } else { - showToast("当前网络错误") - return - } + @objc func createServerBtnClick(sender: UIButton) { + guard let serverName = textField.text, serverName.count > 0 else { return } + + if NEChatDetectNetworkTool.shareInstance.isNetworkRecahability() { + sender.isEnabled = false + } else { + showToast(localizable("network_error")) + return + } - let param = CreateServerParam(name: textField.text!, icon: headImageUrl ?? "") - serverViewModel.createServer(parameter: param) { error, result in + let param = CreateServerParam(name: textField.text!, icon: headImageUrl ?? "") + serverViewModel.createServer(parameter: param) { error, result in - if error != nil { - NELog.errorLog(self.tag, desc: "❌createServer failed,error = \(error!)") + if error != nil { + NELog.errorLog(self.tag, desc: "❌createServer failed,error = \(error!)") + } else { + // 创建服务器成功后,默认创建好两个频道 + if let serverId = result?.server?.serverId { + NotificationCenter.default.post( + name: NotificationName.createServer, + object: serverId + ) + self.navigationController?.dismiss(animated: true, completion: nil) } else { - // 创建服务器成功后,默认创建好两个频道 - if let serverId = result?.server?.serverId { - NotificationCenter.default.post( - name: NotificationName.createServer, - object: serverId - ) - self.navigationController?.dismiss(animated: true, completion: nil) - } else { - print("serverId is nil") - return - } + print("serverId is nil") + return } } - // 应对wifi切换4G请求没有回调的处理结果 - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - sender.isEnabled = true - } } - - //MARK : UITextFieldDelegate - public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, - replacementString string: String) -> Bool { - let text = "\(textField.text ?? "")\(string)" - if text.count > 50 { - showToast("服务器命名不超过50个字符") - return false - } - - return true + // 应对wifi切换4G请求没有回调的处理结果 + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + sender.isEnabled = true } + } - // Upload the picture - @objc func uploadBgViewClick(sender: UIButton) { - showBottomAlert(self) - } + // MARK: UITextFieldDelegate - @objc func textContentChanged() { - if textField.text?.count != 0 { - bottomBtn.isEnabled = true - bottomBtn.backgroundColor = HexRGB(0x337EFF) - } else { - bottomBtn.isEnabled = false - bottomBtn.backgroundColor = HexRGBAlpha(0x337EFF, 0.5) - } + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, + replacementString string: String) -> Bool { + let text = "\(textField.text ?? "")\(string)" + if text.count > 50 { + showToast(localizable("serverName_limit")) + return false } - // MARK: UIImagePickerControllerDelegate + return true + } + + // Upload the picture + @objc func uploadBgViewClick(sender: UIButton) { + showBottomAlert(self) + } - func imagePickerController(_ picker: UIImagePickerController, - didFinishPickingMediaWithInfo info: [UIImagePickerController - .InfoKey: Any]) { - let image: UIImage = info[UIImagePickerController.InfoKey.editedImage] as! UIImage - uploadHeadImage(image: image) - dismiss(animated: true, completion: nil) + @objc func textContentChanged() { + if textField.text?.count != 0 { + bottomBtn.isEnabled = true + bottomBtn.backgroundColor = HexRGB(0x337EFF) + } else { + bottomBtn.isEnabled = false + bottomBtn.backgroundColor = HexRGBAlpha(0x337EFF, 0.5) } + } + + // MARK: UIImagePickerControllerDelegate - public func uploadHeadImage(image: UIImage) { - view.makeToastActivity(.center) - if let imageData = image.jpegData(compressionQuality: 0.6) as NSData? { - let filePath = NSHomeDirectory().appending("/Documents/") - .appending(IMKitLoginManager.instance.imAccid) - let succcess = imageData.write(toFile: filePath, atomically: true) - - if succcess { - NIMSDK.shared().resourceManager - .upload(filePath, progress: nil) { urlString, error in - if error == nil { - // 显示设置的照片 - self.selectHeadImage.image = image - self.headImageUrl = urlString - print("upload image success") - } else { - print("upload image failed,error = \(error!)") - } - self.view.hideToastActivity() + func imagePickerController(_ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController + .InfoKey: Any]) { + let image: UIImage = info[UIImagePickerController.InfoKey.editedImage] as! UIImage + uploadHeadImage(image: image) + dismiss(animated: true, completion: nil) + } + + public func uploadHeadImage(image: UIImage) { + view.makeToastActivity(.center) + if let imageData = image.jpegData(compressionQuality: 0.6) as NSData? { + let filePath = NSHomeDirectory().appending("/Documents/") + .appending(IMKitEngine.instance.imAccid) + let succcess = imageData.write(toFile: filePath, atomically: true) + + if succcess { + NIMSDK.shared().resourceManager + .upload(filePath, progress: nil) { urlString, error in + if error == nil { + // 显示设置的照片 + self.selectHeadImage.image = image + self.headImageUrl = urlString + print("upload image success") + } else { + print("upload image failed,error = \(error!)") } - } + self.view.hideToastActivity() + } } } + } } - diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/QChatHomeViewController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/QChatHomeViewController.swift index 87ce1cef..c1b20546 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/QChatHomeViewController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/Controller/QChatHomeViewController.swift @@ -143,7 +143,6 @@ public class QChatHomeViewController: UIViewController, ViewModelDelegate { weak var weakSelf = self view.viewmodel = serverViewModel view.setUpBlock = { () in - print("设置服务器") if weakSelf?.serverListArray.count == 0 { return } @@ -315,8 +314,8 @@ extension QChatHomeViewController: NIMQChatMessageManagerDelegate { self.channelChange(notificationInfo: systemNotification) case .serverMemberKick, .serverMemberInviteDone: - if systemNotification.fromAccount != IMKitLoginManager.instance.imAccid, - (systemNotification.toAccids?.contains(IMKitLoginManager.instance.imAccid)) != + if systemNotification.fromAccount != IMKitEngine.instance.imAccid, + (systemNotification.toAccids?.contains(IMKitEngine.instance.imAccid)) != nil { self.requestData(timeTag: 0) } @@ -326,7 +325,7 @@ extension QChatHomeViewController: NIMQChatMessageManagerDelegate { selectIndex = 0 self.requestData(timeTag: 0) } else { - if systemNotification.fromAccount == IMKitLoginManager.instance.imAccid { + if systemNotification.fromAccount == IMKitEngine.instance.imAccid { selectIndex = 0 self.requestData(timeTag: 0) } diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/View/InviteMemberView.swift b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/View/InviteMemberView.swift index 17def387..9795bead 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/View/InviteMemberView.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/View/InviteMemberView.swift @@ -46,7 +46,7 @@ class InviteMemberView: UIView { private lazy var content: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false - label.text = localizable("请求已发送") + label.text = localizable("request_sended") label.font = DefaultTextFont(16) label.textColor = UIColor.ne_darkText return label diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/View/NEHomeChannelView.swift b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/View/NEHomeChannelView.swift index 3d7987c9..cd32fda3 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/View/NEHomeChannelView.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/View/NEHomeChannelView.swift @@ -113,7 +113,7 @@ class NEHomeChannelView: UIView { } case .updateChannelCategoryBlackWhiteRole: if noticeInfo.serverId == qchatServerModel?.serverId, - (noticeInfo.toAccids?.contains(IMKitLoginManager.instance.imAccid)) != nil { + (noticeInfo.toAccids?.contains(IMKitEngine.instance.imAccid)) != nil { requestData(timeTag: 0) } @@ -151,7 +151,7 @@ class NEHomeChannelView: UIView { private lazy var subTitleLable: UILabel = { let title = UILabel() title.translatesAutoresizingMaskIntoConstraints = false - title.text = "消息频道" + title.text = localizable("message_channel") title.textColor = PlaceholderTextColor title.font = DefaultTextFont(14) return title @@ -189,7 +189,7 @@ class NEHomeChannelView: UIView { private lazy var emptyView: EmptyDataView = { let view = EmptyDataView( imageName: "channel_noMoreData", - content: "该服务器下暂无频道", + content: localizable("server_nochannel"), frame: tableView.bounds ) view.translatesAutoresizingMaskIntoConstraints = false @@ -231,7 +231,7 @@ extension NEHomeChannelView { self.channelArray.removeAll() self.channelArray = dataArray if dataArray.isEmpty { - emptyView.setttingContent(content: "该服务器下暂无频道") + emptyView.setttingContent(content: localizable("server_nochannel")) emptyView.setEmptyImage(name: "channel_noMoreData") emptyView.isHidden = false } else { @@ -257,7 +257,7 @@ extension NEHomeChannelView { subTitleLable.isHidden = true addChannelBtn.isHidden = true emptyView.isHidden = false - emptyView.setttingContent(content: "暂无服务器,\n赶紧去添加心仪的服务器吧") + emptyView.setttingContent(content: localizable("add_favorite_service")) emptyView.setEmptyImage(name: "servers_noMore") } diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/View/NESearchServerCell.swift b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/View/NESearchServerCell.swift index 97ac73d4..8f7459a3 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/View/NESearchServerCell.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/QChatHomePage/View/NESearchServerCell.swift @@ -34,7 +34,7 @@ class NESearchServerCell: UITableViewCell { let item = QChatGetServerMemberItem( serverId: serverId, - accid: IMKitLoginManager.instance.imAccid + accid: IMKitEngine.instance.imAccid ) let param = QChatGetServerMembersParam(serverAccIds: [item]) @@ -178,7 +178,7 @@ class NESearchServerCell: UITableViewCell { private lazy var rightContent: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false - label.text = localizable("已申请") + label.text = localizable("applied") label.font = DefaultTextFont(12) label.textColor = UIColor.ne_emptyTitleColor label.isHidden = true @@ -195,7 +195,7 @@ class NESearchServerCell: UITableViewCell { private lazy var joinBtn: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false - button.setTitle(localizable("加入"), for: .normal) + button.setTitle(localizable("join"), for: .normal) button.setTitleColor(UIColor.white, for: .normal) button.titleLabel?.font = DefaultTextFont(12) button.backgroundColor = HexRGB(0x337EFF) diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Server/Model/IdGroupModel.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Server/Model/IdGroupModel.swift index c40eaa31..2603c6d8 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Server/Model/IdGroupModel.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Server/Model/IdGroupModel.swift @@ -9,13 +9,6 @@ import NEKitCoreIM class IdGroupModel { var idName: String? var subTitle: String? -// { -// didSet { -// if let s = subTitle, s == "0人" { -// -// } -// } -// } var uid: Int? var isSelect = false var cornerType: CornerType = .none @@ -32,9 +25,5 @@ class IdGroupModel { } else if let type = serverRole.type, type == .custom { subTitle = "\(serverRole.memberCount ?? 0)人" } - -// if let s = subTitle, s == "0人" { -// -// } } } diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Server/Model/PermissionModel.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Server/Model/PermissionModel.swift index 63abac2f..4197eefa 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Server/Model/PermissionModel.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Server/Model/PermissionModel.swift @@ -74,7 +74,7 @@ class PermissionModel: NSObject { #keyPath(PermissionModel.modifyOwnServer): localizable("qchat_modify_own_server"), #keyPath(PermissionModel.modifyOthersServer): localizable("qchat_modify_other_server"), #keyPath(PermissionModel.inviteMember): localizable("qchat_invite_member"), - #keyPath(PermissionModel.kickout): localizable("qchat_kickout_member"), + #keyPath(PermissionModel.kickout): localizable("qchat_kick_out"), #keyPath(PermissionModel.managerBlackAndWhite): localizable("qchat_manager_channel_list"), ] diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatCreateGroupViewController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatCreateGroupViewController.swift index fbf41547..1debedbc 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatCreateGroupViewController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatCreateGroupViewController.swift @@ -9,7 +9,9 @@ import NEKitCoreIM typealias CreateCompletion = () -> Void public class QChatCreateGroupViewController: NEBaseTableViewController, - QChatMemberSelectControllerDelegate,UITableViewDataSource, UITableViewDelegate,ViewModelDelegate, QChatTextEditCellDelegate { + QChatMemberSelectControllerDelegate, UITableViewDataSource, UITableViewDelegate, + ViewModelDelegate, + QChatTextEditCellDelegate { let viewModel = CreateGroupViewModel() var serverId: UInt64? @@ -49,54 +51,28 @@ public class QChatCreateGroupViewController: NEBaseTableViewController, ) } - //MAKR: objc 方法 - @objc func createClick() { - if serverName.count <= 0 { - view.makeToast(localizable("qchat_please_input_role_name")) - return - } - var param = ServerRoleParam() - param.serverId = serverId - param.type = .custom - param.name = serverName - weak var weakSelf = self - print("create role param : ", param) - - viewModel.repo.createRole(param) { error, role in - print("create role : ", error as Any, role) - if let err = error { - weakSelf?.dataDidError(err) - } else { - if let rid = role.roleId, let addMemebers = weakSelf?.viewModel.allUsers, - addMemebers.count > 0 { - weakSelf?.addMember(rid) - } else { - if let block = weakSelf?.completion { - block() - } - weakSelf?.navigationController?.popViewController(animated: true) - } - } - } + // MAKR: objc 方法 + @objc func createClick() { + if serverName.count <= 0 { + view.makeToast(localizable("qchat_please_input_role_name")) + return } - - func addMember(_ roleId: UInt64) { - weak var weakSelf = self - if viewModel.allUsers.count > 0 { - var accids = [String]() - viewModel.allUsers.forEach { user in - if let accid = user.serverMember?.accid { - accids.append(accid) - } - } - var param = AddServerRoleMemberParam() - param.accountArray = accids - param.serverId = serverId - param.roleId = roleId - viewModel.repo.addRoleMember(param) { error, sAccids, fAccids in - if let err = error { - weakSelf?.showToast(err.localizedDescription) - } + var param = ServerRoleParam() + param.serverId = serverId + param.type = .custom + param.name = serverName + weak var weakSelf = self + print("create role param : ", param) + + viewModel.repo.createRole(param) { error, role in + print("create role : ", error as Any, role) + if let err = error { + weakSelf?.dataDidError(err) + } else { + if let rid = role.roleId, let addMemebers = weakSelf?.viewModel.allUsers, + addMemebers.count > 0 { + weakSelf?.addMember(rid) + } else { if let block = weakSelf?.completion { block() } @@ -104,171 +80,194 @@ public class QChatCreateGroupViewController: NEBaseTableViewController, } } } -//MARK : - public func filterMembers(accid: [String]?, _ filterMembers: @escaping ([String]?) -> Void) { - var dic = [String: String]() + } + + func addMember(_ roleId: UInt64) { + weak var weakSelf = self + if viewModel.allUsers.count > 0 { + var accids = [String]() viewModel.allUsers.forEach { user in - if let aid = user.accid { - dic[aid] = aid + if let accid = user.serverMember?.accid { + accids.append(accid) } } - var retArray = [String]() - accid?.forEach { aid in - if dic[aid] != nil { - retArray.append(aid) + var param = AddServerRoleMemberParam() + param.accountArray = accids + param.serverId = serverId + param.roleId = roleId + viewModel.repo.addRoleMember(param) { error, sAccids, fAccids in + if let err = error { + weakSelf?.showToast(err.localizedDescription) + } + if let block = weakSelf?.completion { + block() } + weakSelf?.navigationController?.popViewController(animated: true) } - filterMembers(retArray) - - // filterMembers(accid) } + } + // MARK: - func textDidChange(_ textField: UITextField) { - if let text = textField.text { - serverName = text + public func filterMembers(accid: [String]?, _ filterMembers: @escaping ([String]?) -> Void) { + var dic = [String: String]() + viewModel.allUsers.forEach { user in + if let aid = user.accid { + dic[aid] = aid } - print("text change: ", textField.text as Any) } - - public func dataDidError(_ error: Error) { - UIApplication.shared.keyWindow?.endEditing(true) - view.makeToast(error.localizedDescription) + var retArray = [String]() + accid?.forEach { aid in + if dic[aid] != nil { + retArray.append(aid) + } } + filterMembers(retArray) - public func dataDidChange() { - tableView.reloadData() - } + // filterMembers(accid) + } - public func numberOfSections(in tableView: UITableView) -> Int { - let count = 3 - // if viewModel.limitUsers.count < viewModel.allUsers.count { - // count = count + 1 - // } - return count + func textDidChange(_ textField: UITextField) { + if let text = textField.text { + serverName = text } + print("text change: ", textField.text as Any) + } - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if section == 0 || section == 1 { - return 1 - } else if section == 2 { - return viewModel.allUsers.count - } else if section == 3 { - return 0 - } + public func dataDidError(_ error: Error) { + UIApplication.shared.keyWindow?.endEditing(true) + view.makeToast(error.localizedDescription) + } + + public func dataDidChange() { + tableView.reloadData() + } + + public func numberOfSections(in tableView: UITableView) -> Int { + let count = 3 + // if viewModel.limitUsers.count < viewModel.allUsers.count { + // count = count + 1 + // } + return count + } + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if section == 0 || section == 1 { + return 1 + } else if section == 2 { + return viewModel.allUsers.count + } else if section == 3 { return 0 } + return 0 + } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if indexPath.section == 0 { - let cell: QChatTextEditCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatTextEditCell.self)", - for: indexPath - ) as! QChatTextEditCell - cell.textFied.placeholder = localizable("qchat_please_input_role_name") - cell.delegate = self - cell.limit = 20 - cell.cornerType = CornerType.bottomLeft.union(CornerType.bottomRight) - .union(CornerType.topLeft).union(CornerType.topRight) - return cell - } else if indexPath.section == 1 { - let cell: QChatTextArrowCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatTextArrowCell.self)", - for: indexPath - ) as! QChatTextArrowCell - cell.titleLabel.text = localizable("qchat_add_member") - cell.cornerType = CornerType.bottomLeft.union(CornerType.bottomRight) - .union(CornerType.topLeft).union(CornerType.topRight) - return cell - } else if indexPath.section == 2 { - let cell: QChatIdGroupMemberCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatIdGroupMemberCell.self)", - for: indexPath - ) as! QChatIdGroupMemberCell - let user = viewModel.allUsers[indexPath.row] - cell.cornerType = user.cornerType - cell.user = user - return cell - } else if indexPath.section == 3 { - let cell: QChatUnfoldCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatUnfoldCell.self)", - for: indexPath - ) as! QChatUnfoldCell - cell.contentLabel.text = "更多(共\(viewModel.allUsers.count))人" - cell.cornerType = CornerType.bottomLeft.union(CornerType.bottomRight) - return cell - } - return UITableViewCell() + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if indexPath.section == 0 { + let cell: QChatTextEditCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatTextEditCell.self)", + for: indexPath + ) as! QChatTextEditCell + cell.textFied.placeholder = localizable("qchat_please_input_role_name") + cell.delegate = self + cell.limit = 20 + cell.cornerType = CornerType.bottomLeft.union(CornerType.bottomRight) + .union(CornerType.topLeft).union(CornerType.topRight) + return cell + } else if indexPath.section == 1 { + let cell: QChatTextArrowCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatTextArrowCell.self)", + for: indexPath + ) as! QChatTextArrowCell + cell.titleLabel.text = localizable("add_member") + cell.cornerType = CornerType.bottomLeft.union(CornerType.bottomRight) + .union(CornerType.topLeft).union(CornerType.topRight) + return cell + } else if indexPath.section == 2 { + let cell: QChatIdGroupMemberCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatIdGroupMemberCell.self)", + for: indexPath + ) as! QChatIdGroupMemberCell + let user = viewModel.allUsers[indexPath.row] + cell.cornerType = user.cornerType + cell.user = user + return cell + } else if indexPath.section == 3 { + let cell: QChatUnfoldCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatUnfoldCell.self)", + for: indexPath + ) as! QChatUnfoldCell + cell.contentLabel.text = "更多(共\(viewModel.allUsers.count))人" + cell.cornerType = CornerType.bottomLeft.union(CornerType.bottomRight) + return cell } + return UITableViewCell() + } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if indexPath.section == 1 { - let memberSelect = QChatMemberSelectController() - memberSelect.serverId = serverId - memberSelect.delegate = self - // memberSelect.selectType = .ServerMember - weak var weakSelf = self - memberSelect.completion = { datas in - if datas.count > 0 { - weakSelf?.viewModel.addMembers(datas) - } + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.section == 1 { + let memberSelect = QChatMemberSelectController() + memberSelect.serverId = serverId + memberSelect.delegate = self + // memberSelect.selectType = .ServerMember + weak var weakSelf = self + memberSelect.completion = { datas in + if datas.count > 0 { + weakSelf?.viewModel.addMembers(datas) } - navigationController?.pushViewController(memberSelect, animated: true) + } + navigationController?.pushViewController(memberSelect, animated: true) - } else if indexPath.section == 2 { - // 编辑成员临时入口 + } else if indexPath.section == 2 { + // 编辑成员临时入口 - // let user = viewModel.limitUsers[indexPath.row] - // let editMember = QChatEditMemberViewController() - // editMember.user = user - // navigationController?.pushViewController(editMember, animated: true) + // let user = viewModel.limitUsers[indexPath.row] + // let editMember = QChatEditMemberViewController() + // editMember.user = user + // navigationController?.pushViewController(editMember, animated: true) - viewModel.removeData(indexPath.row) - } else if indexPath.section == 3 { - viewModel.loadAllData() - tableView.reloadData() - } + viewModel.removeData(indexPath.row) + } else if indexPath.section == 3 { + viewModel.loadAllData() + tableView.reloadData() } + } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - 50 - } + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + 50 + } - public func tableView(_ tableView: UITableView, - heightForHeaderInSection section: Int) -> CGFloat { - if section == 0 || section == 1 { - return 40.0 - } else if section == 2 { - return 16 - } - return 0 + public func tableView(_ tableView: UITableView, + heightForHeaderInSection section: Int) -> CGFloat { + if section == 0 || section == 1 { + return 40.0 + } else if section == 2 { + return 16 } + return 0 + } - public func tableView(_ tableView: UITableView, - viewForHeaderInSection section: Int) -> UIView? { - let header = QChatHeaderView() - if section == 0 { - header.titleLabel.text = localizable("qchat_group_name") - return header - } - - if section == 1 { - header.titleLabel.text = localizable("qchat_manager_member") - return header - } - - if section == 2 { - let space = UIView() - space.backgroundColor = .clear - return space - } - - return nil + public func tableView(_ tableView: UITableView, + viewForHeaderInSection section: Int) -> UIView? { + let header = QChatHeaderView() + if section == 0 { + header.titleLabel.text = localizable("qchat_group_name") + return header } -} - + if section == 1 { + header.titleLabel.text = localizable("qchat_manager_member") + return header + } + if section == 2 { + let space = UIView() + space.backgroundColor = .clear + return space + } + return nil + } +} diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatEditMemberViewController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatEditMemberViewController.swift index 251cdaf9..bc0bd0e6 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatEditMemberViewController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatEditMemberViewController.swift @@ -13,7 +13,8 @@ typealias EditMemberChange = () -> Void typealias EditMemberDelete = () -> Void -public class QChatEditMemberViewController: NEBaseTableViewController,UITableViewDataSource, UITableViewDelegate,ViewModelDelegate, QChatTextEditCellDelegate { +public class QChatEditMemberViewController: NEBaseTableViewController, UITableViewDataSource, + UITableViewDelegate, ViewModelDelegate, QChatTextEditCellDelegate { var user: UserInfo? var editAble = false var showAll = false @@ -84,24 +85,39 @@ public class QChatEditMemberViewController: NEBaseTableViewController,UITableVie // Pass the selected object to the new view controller. } */ - //MARK: objc 方法 - @objc func rightBtnClick(_ btn: ExpandButton) { - if btn.isSelected == true { - print("to save") - if nickName.count <= 0 { - showToast("昵称不能为空") - return - } - guard let accid = user?.accid else { - showToast("accid 不能为空") - return - } + // MARK: objc 方法 + + @objc func rightBtnClick(_ btn: ExpandButton) { + if btn.isSelected == true { + print("to save") + if nickName.count <= 0 { + showToast(localizable("nickName_not_empty")) + return + } - weak var weakSelf = self + guard let accid = user?.accid else { + showToast(localizable("accid_not_empty")) + return + } - if IMKitLoginManager.instance.isMySelf(accid) == true { - viewModel.updateMyMember(user?.serverMember?.serverId, nickName) { error, member in + weak var weakSelf = self + + if IMKitEngine.instance.isMySelf(accid) == true { + viewModel.updateMyMember(user?.serverMember?.serverId, nickName) { error, member in + if let err = error { + weakSelf?.showToast(err.localizedDescription) + } else { + weakSelf?.navigationController?.popViewController(animated: true) + if let block = weakSelf?.changeCompletion { + block() + } + } + } + } else { + viewModel + .updateMember(user?.serverMember?.serverId, nickName, + user?.accid) { error, member in if let err = error { weakSelf?.showToast(err.localizedDescription) } else { @@ -111,273 +127,257 @@ public class QChatEditMemberViewController: NEBaseTableViewController,UITableVie } } } - } else { - viewModel - .updateMember(user?.serverMember?.serverId, nickName, - user?.accid) { error, member in - if let err = error { - weakSelf?.showToast(err.localizedDescription) - } else { - weakSelf?.navigationController?.popViewController(animated: true) - if let block = weakSelf?.changeCompletion { - block() - } - } - } - } - - } else { - btn.isSelected = true - editAble = true - btn.setTitle(localizable("qchat_save"), for: .normal) - viewModel.showServerData() } - } - - //MARK: UITableViewDataSource, UITableViewDelegate,ViewModelDelegate, QChatTextEditCellDelegate - func textDidChange(_ textField: UITextField) { - print("edit mebmer name change : ", textField.text as Any) - if let text = textField.text { - nickName = text - } - } - public func dataDidChange() { - tableView.reloadData() + } else { + btn.isSelected = true + editAble = true + btn.setTitle(localizable("qchat_save"), for: .normal) + viewModel.showServerData() } + } - public func dataDidError(_ error: Error) { - showToast(error.localizedDescription) - } + // MARK: UITableViewDataSource, UITableViewDelegate,ViewModelDelegate, QChatTextEditCellDelegate - public func numberOfSections(in tableView: UITableView) -> Int { - 5 + func textDidChange(_ textField: UITextField) { + print("edit mebmer name change : ", textField.text as Any) + if let text = textField.text { + nickName = text } + } - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if section == 0 || section == 1 { - return 1 - } else if section == 2 { - return showAll ? viewModel.allIdGroups.count : viewModel.limitIdGroups.count - } else if section == 3 { - if viewModel.limitIdGroups.count < viewModel.allIdGroups.count { - return 1 - } - } else if section == 4 { + public func dataDidChange() { + tableView.reloadData() + } + + public func dataDidError(_ error: Error) { + showToast(error.localizedDescription) + } + + public func numberOfSections(in tableView: UITableView) -> Int { + 5 + } + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if section == 0 || section == 1 { + return 1 + } else if section == 2 { + return showAll ? viewModel.allIdGroups.count : viewModel.limitIdGroups.count + } else if section == 3 { + if viewModel.limitIdGroups.count < viewModel.allIdGroups.count { return 1 } - return 0 + } else if section == 4 { + return 1 } + return 0 + } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if indexPath.section == 0 { - let cell: QChatHeaderCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatHeaderCell.self)", - for: indexPath - ) as! QChatHeaderCell - cell.cornerType = CornerType.topLeft.union(CornerType.topRight) - .union(CornerType.bottomLeft).union(CornerType.bottomRight) - cell.user = user - return cell - } else if indexPath.section == 1 { - let cell: QChatTextEditCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatTextEditCell.self)", - for: indexPath - ) as! QChatTextEditCell + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if indexPath.section == 0 { + let cell: QChatHeaderCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatHeaderCell.self)", + for: indexPath + ) as! QChatHeaderCell + cell.cornerType = CornerType.topLeft.union(CornerType.topRight) + .union(CornerType.bottomLeft).union(CornerType.bottomRight) + cell.user = user + return cell + } else if indexPath.section == 1 { + let cell: QChatTextEditCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatTextEditCell.self)", + for: indexPath + ) as! QChatTextEditCell + cell.cornerType = CornerType.topLeft.union(CornerType.topRight) + .union(CornerType.bottomLeft).union(CornerType.bottomRight) + cell.delegate = self + cell.editTotast = localizable("modify_nickname") + cell.canEdit = editAble + if nickName.count > 0 { + cell.textFied.text = nickName + } else { + cell.textFied.text = nil + } + cell.textFied.placeholder = localizable("qcaht_edit_nickname") + return cell + } else if indexPath.section == 2 { + let cell: QChatIdGroupSelectCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatIdGroupSelectCell.self)", + for: indexPath + ) as! QChatIdGroupSelectCell + let group = showAll ? viewModel.allIdGroups[indexPath.row] : viewModel + .limitIdGroups[indexPath.row] + cell.tailImage.isHidden = !editAble + if let type = group.role?.type, type == .everyone { + cell.tailImage.isHidden = true + } + let exist = viewModel.checkoutCurrentUserRole(group.role?.roleId) + print("checkoutCurrentUserRole name : \(group.idName ?? "") exist : \(exist)") + group.isSelect = exist + cell.group = group + return cell + } else if indexPath.section == 3 { + let cell: QChatUnfoldCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatUnfoldCell.self)", + for: indexPath + ) as! QChatUnfoldCell + cell.contentLabel + .text = showAll ? "收起(共\(viewModel.allIdGroups.count)个)" : + "更多(共\(viewModel.allIdGroups.count)个)" + if showAll { + cell.changeToArrowUp() + } else { + cell.changeToArrowDown() + } + cell.cornerType = CornerType.bottomLeft.union(CornerType.bottomRight) + return cell + } else if indexPath.section == 4 { + let cell: QChatDestructiveCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatDestructiveCell.self)", + for: indexPath + ) as! QChatDestructiveCell + if indexPath.row == 0 { cell.cornerType = CornerType.topLeft.union(CornerType.topRight) .union(CornerType.bottomLeft).union(CornerType.bottomRight) - cell.delegate = self - cell.editTotast = "请点击编辑后修改昵称" - cell.canEdit = editAble - if nickName.count > 0 { - cell.textFied.text = nickName - } else { - cell.textFied.text = nil - } - cell.textFied.placeholder = localizable("qcaht_edit_nickname") - return cell - } else if indexPath.section == 2 { - let cell: QChatIdGroupSelectCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatIdGroupSelectCell.self)", - for: indexPath - ) as! QChatIdGroupSelectCell - let group = showAll ? viewModel.allIdGroups[indexPath.row] : viewModel - .limitIdGroups[indexPath.row] - cell.tailImage.isHidden = !editAble - if let type = group.role?.type, type == .everyone { - cell.tailImage.isHidden = true - } - let exist = viewModel.checkoutCurrentUserRole(group.role?.roleId) - print("checkoutCurrentUserRole name : \(group.idName ?? "") exist : \(exist)") - group.isSelect = exist - cell.group = group - return cell - } else if indexPath.section == 3 { - let cell: QChatUnfoldCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatUnfoldCell.self)", - for: indexPath - ) as! QChatUnfoldCell - cell.contentLabel - .text = showAll ? "收起(共\(viewModel.allIdGroups.count)个)" : - "更多(共\(viewModel.allIdGroups.count)个)" - if showAll { - cell.changeToArrowUp() - } else { - cell.changeToArrowDown() + cell.redTextLabel.text = "\(localizable("qchat_kick_out")) \(user?.nickName ?? "")" + if getKickDisable() { + cell.changeDisableTextColor() } + } else if indexPath.row == 1 { cell.cornerType = CornerType.bottomLeft.union(CornerType.bottomRight) - return cell - } else if indexPath.section == 4 { - let cell: QChatDestructiveCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatDestructiveCell.self)", - for: indexPath - ) as! QChatDestructiveCell - if indexPath.row == 0 { - cell.cornerType = CornerType.topLeft.union(CornerType.topRight) - .union(CornerType.bottomLeft).union(CornerType.bottomRight) - cell.redTextLabel.text = "\(localizable("qchat_kick_out")) \(user?.nickName ?? "")" - if getKickDisable() { - cell.changeDisableTextColor() - } - } else if indexPath.row == 1 { - cell.cornerType = CornerType.bottomLeft.union(CornerType.bottomRight) - cell.redTextLabel.text = "\(localizable("qchat_prohibit")) \(user?.nickName ?? "")" - cell.changeEnableTextColor() - } - return cell + cell.redTextLabel.text = "\(localizable("qchat_prohibit")) \(user?.nickName ?? "")" + cell.changeEnableTextColor() } - - return UITableViewCell() + return cell } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - if indexPath.section == 0 { - return 92 - } - return 50 + return UITableViewCell() + } + + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + if indexPath.section == 0 { + return 92 } + return 50 + } - public func tableView(_ tableView: UITableView, - viewForHeaderInSection section: Int) -> UIView? { - let header = QChatHeaderView() - if section == 1 { - header.titleLabel.text = localizable("qchat_nickname") - return header - } + public func tableView(_ tableView: UITableView, + viewForHeaderInSection section: Int) -> UIView? { + let header = QChatHeaderView() + if section == 1 { + header.titleLabel.text = localizable("qchat_nickname") + return header + } - if section == 2, viewModel.allIdGroups.count > 0 { - header.titleLabel.text = localizable("qchat_id_group") - return header - } - return nil + if section == 2, viewModel.allIdGroups.count > 0 { + header.titleLabel.text = localizable("qchat_id_group") + return header } + return nil + } - public func tableView(_ tableView: UITableView, - heightForHeaderInSection section: Int) -> CGFloat { - if section == 0 { - return 22 - } + public func tableView(_ tableView: UITableView, + heightForHeaderInSection section: Int) -> CGFloat { + if section == 0 { + return 22 + } - if section == 1 { - return 38 - } + if section == 1 { + return 38 + } - if section == 2, viewModel.allIdGroups.count > 0 { - return 38 - } + if section == 2, viewModel.allIdGroups.count > 0 { + return 38 + } - if section == 4 { - return 20 - } - return 0 + if section == 4 { + return 20 } + return 0 + } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if indexPath.section == 1 { - // showToast("请点击编辑后修改昵称") - } else if indexPath.section == 2 { - if editAble == false { - return - } + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.section == 1 { + // showToast("请点击编辑后修改昵称") + } else if indexPath.section == 2 { + if editAble == false { + return + } - let group = showAll == true ? viewModel.allIdGroups[indexPath.row] : viewModel - .limitIdGroups[indexPath.row] + let group = showAll == true ? viewModel.allIdGroups[indexPath.row] : viewModel + .limitIdGroups[indexPath.row] - if let type = group.role?.type, type == .everyone { - return - } - let select = viewModel.checkoutCurrentUserRole(group.role?.roleId) - weak var weakSelf = self - if select == false { - view.makeToastActivity(.center) - viewModel.addMembers(user?.accid, user?.serverId, group.role?.roleId) { - weakSelf?.view.hideToastActivity() - weakSelf?.didChange = true - } - } else { - view.makeToastActivity(.center) - viewModel.remove(user?.accid, user?.serverId, group.role?.roleId) { - weakSelf?.view.hideToastActivity() - weakSelf?.didChange = true - } - } - } else if indexPath.section == 3 { - showAll = !showAll - tableView.reloadData() - } else if indexPath.section == 4 { - weak var weakSelf = self - if getKickDisable() == true { - return + if let type = group.role?.type, type == .everyone { + return + } + let select = viewModel.checkoutCurrentUserRole(group.role?.roleId) + weak var weakSelf = self + if select == false { + view.makeToastActivity(.center) + viewModel.addMembers(user?.accid, user?.serverId, group.role?.roleId) { + weakSelf?.view.hideToastActivity() + weakSelf?.didChange = true } - showAlert(message: "确定踢出当前成员?") { - weakSelf?.kickOutMember() + } else { + view.makeToastActivity(.center) + viewModel.remove(user?.accid, user?.serverId, group.role?.roleId) { + weakSelf?.view.hideToastActivity() + weakSelf?.didChange = true } } + } else if indexPath.section == 3 { + showAll = !showAll + tableView.reloadData() + } else if indexPath.section == 4 { + weak var weakSelf = self + if getKickDisable() == true { + return + } + showAlert(message: localizable("kick_currentMember")) { + weakSelf?.kickOutMember() + } } + } - func didChangeRole() { - if didChange == true { - if let block = changeCompletion { - block() - } + func didChangeRole() { + if didChange == true { + if let block = changeCompletion { + block() } } + } - func getKickDisable() -> Bool { - if let accid = user?.serverMember?.accid { - // if CoreKitEngine.instance.imAccid == accid { - // return true - // } + func getKickDisable() -> Bool { + if let accid = user?.serverMember?.accid { + // if CoreKitEngine.instance.imAccid == accid { + // return true + // } - if IMKitLoginManager.instance.imAccid == accid { - return true - } - if let type = user?.serverMember?.type, type == .owner { - return true - } + if IMKitEngine.instance.imAccid == accid { + return true + } + if let type = user?.serverMember?.type, type == .owner { + return true } - return false } + return false + } - func kickOutMember() { - view.makeToastActivity(.center) - weak var weakSelf = self - viewModel.kickoutMember(user?.serverId, user?.accid) { error in - weakSelf?.view.hideToastActivity() - if let err = error { - weakSelf?.showToast(err.localizedDescription) - } else { - if let block = weakSelf?.deleteCompletion { - block() - } - weakSelf?.navigationController?.popViewController(animated: true) + func kickOutMember() { + view.makeToastActivity(.center) + weak var weakSelf = self + viewModel.kickoutMember(user?.serverId, user?.accid) { error in + weakSelf?.view.hideToastActivity() + if let err = error { + weakSelf?.showToast(err.localizedDescription) + } else { + if let block = weakSelf?.deleteCompletion { + block() } + weakSelf?.navigationController?.popViewController(animated: true) } } - - + } } - - diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatIdGroupSortController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatIdGroupSortController.swift index 725abc19..e43729b1 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatIdGroupSortController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatIdGroupSortController.swift @@ -10,7 +10,8 @@ import NEKitCoreIM typealias SortChange = () -> Void -public class QChatIdGroupSortController: NEBaseTableViewController,UITableViewDelegate, UITableViewDataSource,ViewModelDelegate { +public class QChatIdGroupSortController: NEBaseTableViewController, UITableViewDelegate, + UITableViewDataSource, ViewModelDelegate { var serverId: UInt64? var isOwer = false @@ -72,134 +73,136 @@ public class QChatIdGroupSortController: NEBaseTableViewController,UITableViewDe navigationController?.popViewController(animated: true) } - //MARK: UITableViewDelegate, UITableViewDataSource, ViewModelDelegate - public func dataDidChange() { - tableView.reloadData() - } + // MARK: UITableViewDelegate, UITableViewDataSource, ViewModelDelegate - public func dataDidError(_ error: Error) { - view.hideToastActivity() - showToast(error.localizedDescription) - } + public func dataDidChange() { + tableView.reloadData() + } - public func numberOfSections(in tableView: UITableView) -> Int { - 1 - } + public func dataDidError(_ error: Error) { + view.hideToastActivity() + showToast(error.localizedDescription) + } - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - // if section == 0 { - // return viewmodel.lockData.count - // }else if section == 1 { - // return viewmodel.datas.count - // } - viewmodel.datas.count - } + public func numberOfSections(in tableView: UITableView) -> Int { + 1 + } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell: QChatSortCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatSortCell.self)", - for: indexPath - ) as! QChatSortCell - // if indexPath.section == 0 { - // let model = viewmodel.lockData[indexPath.row] - // cell.configure(model) - // cell.tailImage.isHighlighted = true - // }else if indexPath.section == 1 { - // if let model = viewmodel.datas[indexPath.row] as? IdGroupModel { - // cell.configure(model) - // cell.tailImage.isHighlighted = !model.hasPermission - // } - // } - if let model = viewmodel.datas[indexPath.row] as? IdGroupModel { - cell.configure(model) - cell.tailImage.isHighlighted = !model.hasPermission - } + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + // if section == 0 { + // return viewmodel.lockData.count + // }else if section == 1 { + // return viewmodel.datas.count + // } + viewmodel.datas.count + } - return cell + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell: QChatSortCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatSortCell.self)", + for: indexPath + ) as! QChatSortCell + // if indexPath.section == 0 { + // let model = viewmodel.lockData[indexPath.row] + // cell.configure(model) + // cell.tailImage.isHighlighted = true + // }else if indexPath.section == 1 { + // if let model = viewmodel.datas[indexPath.row] as? IdGroupModel { + // cell.configure(model) + // cell.tailImage.isHighlighted = !model.hasPermission + // } + // } + if let model = viewmodel.datas[indexPath.row] as? IdGroupModel { + cell.configure(model) + cell.tailImage.isHighlighted = !model.hasPermission } - public func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, - to destinationIndexPath: IndexPath) { - print("source :", sourceIndexPath.row, " destionation :", destinationIndexPath.row) - - if sourceIndexPath.row > destinationIndexPath.row { - let src_model = viewmodel.datas.object(at: sourceIndexPath.row) - viewmodel.datas.remove(src_model) - viewmodel.datas.insert(src_model, at: destinationIndexPath.row) - } else { - let src_model = viewmodel.datas.object(at: sourceIndexPath.row) - viewmodel.datas.insert(src_model, at: destinationIndexPath.row + 1) - viewmodel.datas.removeObject(at: sourceIndexPath.row) - } + return cell + } - viewmodel.datas.forEach { user in - if let u = user as? IdGroupModel { - print("change name : ", u.idName as Any) - print("change p: ", u.role?.priority as Any) - } - } + public func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, + to destinationIndexPath: IndexPath) { + print("source :", sourceIndexPath.row, " destionation :", destinationIndexPath.row) + + if sourceIndexPath.row > destinationIndexPath.row { + let src_model = viewmodel.datas.object(at: sourceIndexPath.row) + viewmodel.datas.remove(src_model) + viewmodel.datas.insert(src_model, at: destinationIndexPath.row) + } else { + let src_model = viewmodel.datas.object(at: sourceIndexPath.row) + viewmodel.datas.insert(src_model, at: destinationIndexPath.row + 1) + viewmodel.datas.removeObject(at: sourceIndexPath.row) } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - 60 + viewmodel.datas.forEach { user in + if let u = user as? IdGroupModel { + print("change name : ", u.idName as Any) + print("change p: ", u.role?.priority as Any) + } } + } - public func tableView(_ tableView: UITableView, - shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { - false - } + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + 60 + } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if let model = viewmodel.datas[indexPath.row] as? IdGroupModel { - weak var weakSelf = self - if model.hasPermission == true { - showAlert(message: "确定删除 \(model.idName ?? "当前身份组")?") { - weakSelf?.view.makeToastActivity(.center) - weakSelf?.viewmodel.removeRole(weakSelf?.serverId, model.role?.roleId, model) { - weakSelf?.didDelete = true - weakSelf?.view.hideToastActivity() - } + public func tableView(_ tableView: UITableView, + shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { + false + } + + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if let model = viewmodel.datas[indexPath.row] as? IdGroupModel { + weak var weakSelf = self + if model.hasPermission == true { + showAlert( + message: "\(localizable("sure_delete"))\(model.idName ?? localizable("current_identity"))?" + ) { + weakSelf?.view.makeToastActivity(.center) + weakSelf?.viewmodel.removeRole(weakSelf?.serverId, model.role?.roleId, model) { + weakSelf?.didDelete = true + weakSelf?.view.hideToastActivity() } } } } + } - public func tableView(_ tableView: UITableView, - targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, - toProposedIndexPath proposedDestinationIndexPath: IndexPath) - -> IndexPath { - if isOwer == true { - return proposedDestinationIndexPath - } else { - if let model = viewmodel.datas[proposedDestinationIndexPath.row] as? IdGroupModel { - if model.hasPermission == true { - return proposedDestinationIndexPath - } + public func tableView(_ tableView: UITableView, + targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, + toProposedIndexPath proposedDestinationIndexPath: IndexPath) + -> IndexPath { + if isOwer == true { + return proposedDestinationIndexPath + } else { + if let model = viewmodel.datas[proposedDestinationIndexPath.row] as? IdGroupModel { + if model.hasPermission == true { + return proposedDestinationIndexPath } } - return sourceIndexPath } + return sourceIndexPath + } - public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - if let model = viewmodel.datas[indexPath.row] as? IdGroupModel { - return model.hasPermission - } - return true + public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + if let model = viewmodel.datas[indexPath.row] as? IdGroupModel { + return model.hasPermission } + return true + } - public func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { - if let model = viewmodel.datas[indexPath.row] as? IdGroupModel { - return model.hasPermission - } - return true + public func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { + if let model = viewmodel.datas[indexPath.row] as? IdGroupModel { + return model.hasPermission } + return true + } - public func tableView(_ tableView: UITableView, - editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { - UITableViewCell.EditingStyle.none - } + public func tableView(_ tableView: UITableView, + editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell + .EditingStyle { + UITableViewCell.EditingStyle.none + } } - - diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatIdGroupViewController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatIdGroupViewController.swift index 89e2f36e..82c439c0 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatIdGroupViewController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatIdGroupViewController.swift @@ -6,7 +6,8 @@ import UIKit import MJRefresh -public class QChatIdGroupViewController: NEBaseTableViewController,UITableViewDelegate, UITableViewDataSource,ViewModelDelegate { +public class QChatIdGroupViewController: NEBaseTableViewController, UITableViewDelegate, + UITableViewDataSource, ViewModelDelegate { let viewModel = IdGroupViewModel() var isOwner = false var serverid: UInt64? @@ -75,171 +76,171 @@ public class QChatIdGroupViewController: NEBaseTableViewController,UITableViewDe // Pass the selected object to the new view controller. } */ - //MARK: UITableViewDelegate, UITableViewDataSource,ViewModelDelegate - public func dataDidError(_ error: Error) { - print("get roles error : ", error) - NELog.errorLog(className(), desc: "error : \(error)") - view.makeToast(error.localizedDescription) - } - public func dataDidChange() { - tableView.mj_footer?.endRefreshing() - tableView.reloadData() - } + // MARK: UITableViewDelegate, UITableViewDataSource,ViewModelDelegate - public func dataNoMore() { - tableView.mj_footer?.endRefreshingWithNoMoreData() - tableView.mj_footer?.isHidden = true - } + public func dataDidError(_ error: Error) { + print("get roles error : ", error) + NELog.errorLog(className(), desc: "error : \(error)") + view.makeToast(error.localizedDescription) + } - public func numberOfSections(in tableView: UITableView) -> Int { - 3 - } + public func dataDidChange() { + tableView.mj_footer?.endRefreshing() + tableView.reloadData() + } - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if section == 0 { - return viewModel.topDatas.count - } else if section == 1 { - if viewModel.datas.count > 0 { - return viewModel.sortBtnCellDatas.count - } - } else if section == 2 { - return viewModel.datas.count - } - return 0 - } + public func dataNoMore() { + tableView.mj_footer?.endRefreshingWithNoMoreData() + tableView.mj_footer?.isHidden = true + } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if indexPath.section == 0 { - let model = viewModel.topDatas[indexPath.row] - let cell: QChatIdGroupTopCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatIdGroupTopCell.self)", - for: indexPath - ) as! QChatIdGroupTopCell - cell.configure(model) - return cell - } else if indexPath.section == 1 { - let model = viewModel.sortBtnCellDatas[indexPath.row] - let cell: QChatIdGroupSortButtonCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatIdGroupSortButtonCell.self)", - for: indexPath - ) as! QChatIdGroupSortButtonCell - cell.titleLabel.text = model.idName - cell.sortBtn.addTarget(self, action: #selector(toSort), for: .touchUpInside) - return cell - } else if indexPath.section == 2 { - let model = viewModel.datas[indexPath.row] - let cell: QChatIdGroupCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatIdGroupCell.self)", - for: indexPath - ) as! QChatIdGroupCell - cell.configure(model) - return cell - } - return UITableViewCell() - } + public func numberOfSections(in tableView: UITableView) -> Int { + 3 + } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - if indexPath.section == 0 { - return 68 - } else if indexPath.section == 1 { - return 30 - } else if indexPath.section == 2 { - return 60 + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if section == 0 { + return viewModel.topDatas.count + } else if section == 1 { + if viewModel.datas.count > 0 { + return viewModel.sortBtnCellDatas.count } - return 0 + } else if section == 2 { + return viewModel.datas.count } + return 0 + } - public func tableView(_ tableView: UITableView, - heightForHeaderInSection section: Int) -> CGFloat { - if section == 1 { - return 6 - } - return 0 - } + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if indexPath.section == 0 { + let model = viewModel.topDatas[indexPath.row] + let cell: QChatIdGroupTopCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatIdGroupTopCell.self)", + for: indexPath + ) as! QChatIdGroupTopCell + cell.configure(model) + return cell + } else if indexPath.section == 1 { + let model = viewModel.sortBtnCellDatas[indexPath.row] + let cell: QChatIdGroupSortButtonCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatIdGroupSortButtonCell.self)", + for: indexPath + ) as! QChatIdGroupSortButtonCell + cell.titleLabel.text = model.idName + cell.sortBtn.addTarget(self, action: #selector(toSort), for: .touchUpInside) + return cell + } else if indexPath.section == 2 { + let model = viewModel.datas[indexPath.row] + let cell: QChatIdGroupCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatIdGroupCell.self)", + for: indexPath + ) as! QChatIdGroupCell + cell.configure(model) + return cell + } + return UITableViewCell() + } - public func tableView(_ tableView: UITableView, - viewForHeaderInSection section: Int) -> UIView? { - if section == 1 { - let view = UIView(frame: CGRect.zero) - view.backgroundColor = UIColor(hexString: "EFF1F4") - return view - } - return nil + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + if indexPath.section == 0 { + return 68 + } else if indexPath.section == 1 { + return 30 + } else if indexPath.section == 2 { + return 60 } + return 0 + } - public func tableView(_ tableView: UITableView, - heightForFooterInSection section: Int) -> CGFloat { - 0 + public func tableView(_ tableView: UITableView, + heightForHeaderInSection section: Int) -> CGFloat { + if section == 1 { + return 6 } + return 0 + } - public func tableView(_ tableView: UITableView, - viewForFooterInSection section: Int) -> UIView? { - nil + public func tableView(_ tableView: UITableView, + viewForHeaderInSection section: Int) -> UIView? { + if section == 1 { + let view = UIView(frame: CGRect.zero) + view.backgroundColor = UIColor(hexString: "EFF1F4") + return view } + return nil + } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if indexPath.section == 1 { - return - } + public func tableView(_ tableView: UITableView, + heightForFooterInSection section: Int) -> CGFloat { + 0 + } - if indexPath.section == 0 { - let model = viewModel.topDatas[indexPath.row] - toPermission(model) - } else if indexPath.section == 2 { - let model = viewModel.datas[indexPath.row] - toPermission(model) - } - } - - //MARK: objc 方法 - @objc func addClick() { - let create = QChatCreateGroupViewController() - create.serverId = serverid - weak var weakSelf = self - create.completion = { - weakSelf?.refreshData() - } - navigationController?.pushViewController(create, animated: true) - } + public func tableView(_ tableView: UITableView, + viewForFooterInSection section: Int) -> UIView? { + nil + } - @objc func toSort() { - let sort = QChatIdGroupSortController() - sort.serverId = serverid - sort.isOwer = isOwner - weak var weakSelf = self - sort.completion = { - weakSelf?.refreshData() - // weakSelf?.viewModel.datas.removeAll() - // for index in 0.. Void -public class QChatMemberManagerController: NEBaseTableViewController,UITableViewDelegate, UITableViewDataSource,ViewModelDelegate,QChatMemberSelectControllerDelegate { +public class QChatMemberManagerController: NEBaseTableViewController, UITableViewDelegate, + UITableViewDataSource, ViewModelDelegate, QChatMemberSelectControllerDelegate { let viewmodel = MemberManagerViewModel() var memberCount = 0 @@ -69,152 +70,151 @@ public class QChatMemberManagerController: NEBaseTableViewController,UITableView // Pass the selected object to the new view controller. } */ - //MARK: UITableViewDelegate, UITableViewDataSource,ViewModelDelegate,QChatMemberSelectControllerDelegate - - public func filterMembers(accid: [String]?, _ filterMembers: @escaping ([String]?) -> Void) { - var param = GetExistingServerRoleMembersByAccidsParam() - param.serverId = serverId - param.accids = accid - param.roleId = roleId - print("param existing accid : ", accid as Any) - viewmodel.repo.getExistingServerRoleMembersByAccids(param) { error, accids in - print("getExistingServerRoleMembersByAccids : ", accids) - var dic = [String: String]() - var retAccids = [String]() - accids.forEach { aid in - dic[aid] = aid - } - accid?.forEach { aid in - if dic[aid] != nil { - retAccids.append(aid) - } + + // MARK: UITableViewDelegate, UITableViewDataSource,ViewModelDelegate,QChatMemberSelectControllerDelegate + + public func filterMembers(accid: [String]?, _ filterMembers: @escaping ([String]?) -> Void) { + var param = GetExistingServerRoleMembersByAccidsParam() + param.serverId = serverId + param.accids = accid + param.roleId = roleId + print("param existing accid : ", accid as Any) + viewmodel.repo.getExistingServerRoleMembersByAccids(param) { error, accids in + print("getExistingServerRoleMembersByAccids : ", accids) + var dic = [String: String]() + var retAccids = [String]() + accids.forEach { aid in + dic[aid] = aid + } + accid?.forEach { aid in + if dic[aid] != nil { + retAccids.append(aid) } - print("filter members : ", retAccids) - filterMembers(retAccids) } + print("filter members : ", retAccids) + filterMembers(retAccids) } + } - public func dataDidChange() { - view.hideToastActivity() - tableView.mj_footer?.endRefreshing() - tableView.reloadData() - } + public func dataDidChange() { + view.hideToastActivity() + tableView.mj_footer?.endRefreshing() + tableView.reloadData() + } - public func dataNoMore() { - view.hideToastActivity() - tableView.mj_footer?.endRefreshingWithNoMoreData() - tableView.mj_footer?.isHidden = true - } + public func dataNoMore() { + view.hideToastActivity() + tableView.mj_footer?.endRefreshingWithNoMoreData() + tableView.mj_footer?.isHidden = true + } - public func dataDidError(_ error: Error) { - showToast(error.localizedDescription) - } + public func dataDidError(_ error: Error) { + showToast(error.localizedDescription) + } - public func numberOfSections(in tableView: UITableView) -> Int { - 2 - } + public func numberOfSections(in tableView: UITableView) -> Int { + 2 + } - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if section == 0 { - return 1 - } - if section == 1 { - return viewmodel.datas.count - } - return 0 + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if section == 0 { + return 1 } - - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if indexPath.section == 0 { - let cell: QChatPlainTextArrowCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatPlainTextArrowCell.self)", - for: indexPath - ) as! QChatPlainTextArrowCell - cell.titleLabel.text = localizable("qchat_add_member") - cell.detailLabel.text = "\(memberCount)" - return cell - } - - if indexPath.section == 1 { - let cell: QChatMemberManagerCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatMemberManagerCell.self)", - for: indexPath - ) as! QChatMemberManagerCell - let user = viewmodel.datas[indexPath.row] - cell.user = user - return cell - } - - return UITableViewCell() + if section == 1 { + return viewmodel.datas.count } + return 0 + } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - 50 + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if indexPath.section == 0 { + let cell: QChatPlainTextArrowCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatPlainTextArrowCell.self)", + for: indexPath + ) as! QChatPlainTextArrowCell + cell.titleLabel.text = localizable("add_member") + cell.detailLabel.text = "\(memberCount)" + return cell } - public func tableView(_ tableView: UITableView, - heightForHeaderInSection section: Int) -> CGFloat { - 0 + if indexPath.section == 1 { + let cell: QChatMemberManagerCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatMemberManagerCell.self)", + for: indexPath + ) as! QChatMemberManagerCell + let user = viewmodel.datas[indexPath.row] + cell.user = user + return cell } - public func tableView(_ tableView: UITableView, - heightForFooterInSection section: Int) -> CGFloat { - 0 - } + return UITableViewCell() + } - public func tableView(_ tableView: UITableView, - viewForHeaderInSection section: Int) -> UIView? { - nil - } + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + 50 + } - public func tableView(_ tableView: UITableView, - viewForFooterInSection section: Int) -> UIView? { - nil - } + public func tableView(_ tableView: UITableView, + heightForHeaderInSection section: Int) -> CGFloat { + 0 + } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - weak var weakSelf = self - if indexPath.section == 0 { - let memberSelect = QChatMemberSelectController() - memberSelect.delegate = self - memberSelect.serverId = serverId - memberSelect.completion = { users in - print("member manager select: ", users) - weakSelf?.view.makeToastActivity(.center) - weakSelf?.viewmodel - .addMembers(users, weakSelf?.serverId, weakSelf?.roleId) { successCount in - // weakSelf?.view.hideToastActivity() - weakSelf?.showToast(localizable("qchat_add_success")) - if let block = weakSelf?.countChangeBlock, var count = weakSelf?.memberCount { - count = count + successCount - weakSelf?.memberCount = count - block(count) - } + public func tableView(_ tableView: UITableView, + heightForFooterInSection section: Int) -> CGFloat { + 0 + } + + public func tableView(_ tableView: UITableView, + viewForHeaderInSection section: Int) -> UIView? { + nil + } + + public func tableView(_ tableView: UITableView, + viewForFooterInSection section: Int) -> UIView? { + nil + } + + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + weak var weakSelf = self + if indexPath.section == 0 { + let memberSelect = QChatMemberSelectController() + memberSelect.delegate = self + memberSelect.serverId = serverId + memberSelect.completion = { users in + print("member manager select: ", users) + weakSelf?.view.makeToastActivity(.center) + weakSelf?.viewmodel + .addMembers(users, weakSelf?.serverId, weakSelf?.roleId) { successCount in + // weakSelf?.view.hideToastActivity() + weakSelf?.showToast(localizable("qchat_add_success")) + if let block = weakSelf?.countChangeBlock, var count = weakSelf?.memberCount { + count = count + successCount + weakSelf?.memberCount = count + block(count) } - } - navigationController?.pushViewController(memberSelect, animated: true) - } else { - let user = viewmodel.datas[indexPath.row] - showAlert(message: localizable("qchat_sure_delete_user")) { - if let rid = weakSelf?.roleId, let sid = weakSelf?.serverId { - weakSelf?.view.makeToastActivity(.center) - weakSelf?.viewmodel.remove(user, sid, rid) { - weakSelf?.view.hideToastActivity() - weakSelf?.viewmodel.datas.remove(at: indexPath.row) - weakSelf?.tableView.reloadData() - if var count = weakSelf?.memberCount, - let block = weakSelf?.countChangeBlock { - count = count - 1 - weakSelf?.memberCount = count - block(count) - } + } + } + navigationController?.pushViewController(memberSelect, animated: true) + } else { + let user = viewmodel.datas[indexPath.row] + showAlert(message: localizable("qchat_sure_delete_user")) { + if let rid = weakSelf?.roleId, let sid = weakSelf?.serverId { + weakSelf?.view.makeToastActivity(.center) + weakSelf?.viewmodel.remove(user, sid, rid) { + weakSelf?.view.hideToastActivity() + weakSelf?.viewmodel.datas.remove(at: indexPath.row) + weakSelf?.tableView.reloadData() + if var count = weakSelf?.memberCount, + let block = weakSelf?.countChangeBlock { + count = count - 1 + weakSelf?.memberCount = count + block(count) } } } } } + } } - - diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatMemberSelectController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatMemberSelectController.swift index f7ccaaad..b0bf7847 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatMemberSelectController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatMemberSelectController.swift @@ -18,8 +18,10 @@ public protocol QChatMemberSelectControllerDelegate: AnyObject { // case ChannelMember // } -public class QChatMemberSelectController: NEBaseTableViewController, MemberSelectViewModelDelegate,UICollectionViewDelegate, UICollectionViewDataSource, - UICollectionViewDelegateFlowLayout,UITableViewDelegate,UITableViewDataSource,ViewModelDelegate { +public class QChatMemberSelectController: NEBaseTableViewController, MemberSelectViewModelDelegate, + UICollectionViewDelegate, UICollectionViewDataSource, + UICollectionViewDelegateFlowLayout, UITableViewDelegate, UITableViewDataSource, + ViewModelDelegate { let viewmodel = MemberSelectViewModel() var filterBlock: FilterMembersBlock? var completion: SelectMemeberCompletion? @@ -145,132 +147,132 @@ public class QChatMemberSelectController: NEBaseTableViewController, MemberSelec } } } - //MARK: - public func dataDidError(_ error: Error) { - view.makeToast(error.localizedDescription) - } - public func dataDidChange() { - tableView.reloadData() - } + // MARK: - public func collectionView(_ collectionView: UICollectionView, - numberOfItemsInSection section: Int) -> Int { - selectArray.count - } + public func dataDidError(_ error: Error) { + view.makeToast(error.localizedDescription) + } - public func collectionView(_ collectionView: UICollectionView, - cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let user = selectArray[indexPath.row] - let cell = collectionView.dequeueReusableCell( - withReuseIdentifier: "\(NSStringFromClass(QChatUserUnCheckCell.self))", - for: indexPath - ) as? QChatUserUnCheckCell - cell?.configure(user) - return cell ?? UICollectionViewCell() - } + public func dataDidChange() { + tableView.reloadData() + } - public func collectionView(_ collectionView: UICollectionView, - didSelectItemAt indexPath: IndexPath) { - let user = selectArray[indexPath.row] - didUnselectContact(user) - } + public func collectionView(_ collectionView: UICollectionView, + numberOfItemsInSection section: Int) -> Int { + selectArray.count + } - public func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - sizeForItemAt indexPath: IndexPath) -> CGSize { - CGSize(width: 46, height: 52) - } + public func collectionView(_ collectionView: UICollectionView, + cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let user = selectArray[indexPath.row] + let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: "\(NSStringFromClass(QChatUserUnCheckCell.self))", + for: indexPath + ) as? QChatUserUnCheckCell + cell?.configure(user) + return cell ?? UICollectionViewCell() + } - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - viewmodel.datas.count - } + public func collectionView(_ collectionView: UICollectionView, + didSelectItemAt indexPath: IndexPath) { + let user = selectArray[indexPath.row] + didUnselectContact(user) + } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell: QChatSelectedCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatSelectedCell.self)", - for: indexPath - ) as! QChatSelectedCell - let user = viewmodel.datas[indexPath.row] - cell.user = user - return cell - } + public func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath) -> CGSize { + CGSize(width: 46, height: 52) + } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - 62 - } + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + viewmodel.datas.count + } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let user = viewmodel.datas[indexPath.row] - let cell = tableView.cellForRow(at: indexPath) as? QChatSelectedCell + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell: QChatSelectedCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatSelectedCell.self)", + for: indexPath + ) as! QChatSelectedCell + let user = viewmodel.datas[indexPath.row] + cell.user = user + return cell + } - if user.select == true { - cell?.setUnselect() - didUnselectContact(user) - } else { - if selectArray.count >= limit { - // view.makeToast("超出\(limit)人限制") - showToast( - "\(localizable("exceed"))\(limit)\(localizable("person"))\(localizable("limit"))" - ) - return - } - cell?.setSelect() - didSelectContact(user) + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + 62 + } + + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let user = viewmodel.datas[indexPath.row] + let cell = tableView.cellForRow(at: indexPath) as? QChatSelectedCell + + if user.select == true { + cell?.setUnselect() + didUnselectContact(user) + } else { + if selectArray.count >= limit { + // view.makeToast("超出\(limit)人限制") + showToast( + "\(localizable("exceed"))\(limit)\(localizable("person"))\(localizable("limit"))" + ) + return } - // tableView.reloadRows(at: [indexPath], with: .none) + cell?.setSelect() + didSelectContact(user) } + // tableView.reloadRows(at: [indexPath], with: .none) + } - func didSelectContact(_ user: UserInfo) { - user.select = true - if selectArray.contains(where: { c in - user === c - }) == false { - selectArray.append(user) - if let height = collectionHeight?.constant, height <= 0 { - collectionHeight?.constant = 52 - } + func didSelectContact(_ user: UserInfo) { + user.select = true + if selectArray.contains(where: { c in + user === c + }) == false { + selectArray.append(user) + if let height = collectionHeight?.constant, height <= 0 { + collectionHeight?.constant = 52 } - collection.reloadData() - tableView.reloadData() - refreshSelectCount() } + collection.reloadData() + tableView.reloadData() + refreshSelectCount() + } - func didUnselectContact(_ user: UserInfo) { - user.select = false - selectArray.removeAll { c in - user === c - } - if selectArray.count <= 0 { - collection.reloadData() - collectionHeight?.constant = 0 - } + func didUnselectContact(_ user: UserInfo) { + user.select = false + selectArray.removeAll { c in + user === c + } + if selectArray.count <= 0 { collection.reloadData() - tableView.reloadData() - refreshSelectCount() + collectionHeight?.constant = 0 } + collection.reloadData() + tableView.reloadData() + refreshSelectCount() + } - func refreshSelectCount() { - if selectArray.count > 0 { - rightNavBtn.setTitle("\(localizable("qchat_sure"))(\(selectArray.count))", for: .normal) - } else { - rightNavBtn.setTitle(localizable("qchat_sure"), for: .normal) - } + func refreshSelectCount() { + if selectArray.count > 0 { + rightNavBtn.setTitle("\(localizable("qchat_sure"))(\(selectArray.count))", for: .normal) + } else { + rightNavBtn.setTitle(localizable("qchat_sure"), for: .normal) } + } - public func tableView(_ tableView: UITableView, - heightForHeaderInSection section: Int) -> CGFloat { - 0 - } + public func tableView(_ tableView: UITableView, + heightForHeaderInSection section: Int) -> CGFloat { + 0 + } // MARK: MemberSelectViewModelDelegate - func filterMembers(accid: [String]?, _ filterMembers: @escaping ([String]?) -> Void) { - // 查询需要筛选的用户 - delegate?.filterMembers(accid: accid, filterMembers) - } + func filterMembers(accid: [String]?, _ filterMembers: @escaping ([String]?) -> Void) { + // 查询需要筛选的用户 + delegate?.filterMembers(accid: accid, filterMembers) + } } - - diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatPermissionViewController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatPermissionViewController.swift index 061953d4..1eb47c7d 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatPermissionViewController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatPermissionViewController.swift @@ -7,7 +7,8 @@ import UIKit import NEKitCoreIM typealias RoleUpdateCompletion = (_ role: ServerRole) -> Void -public class QChatPermissionViewController: NEBaseTableViewController,UITableViewDelegate, UITableViewDataSource, QChatTextEditCellDelegate, ViewModelDelegate, QChatSwitchCellDelegate { +public class QChatPermissionViewController: NEBaseTableViewController, UITableViewDelegate, + UITableViewDataSource, QChatTextEditCellDelegate, ViewModelDelegate, QChatSwitchCellDelegate { var idGroup: IdGroupModel? let viewmodel = PermissionViewModel() @@ -62,218 +63,217 @@ public class QChatPermissionViewController: NEBaseTableViewController,UITableVie // Pass the selected object to the new view controller. } */ - //MARK: UITableViewDelegate, UITableViewDataSource, QChatTextEditCellDelegate, ViewModelDelegate, QChatSwitchCellDelegate - - func didChangeSwitchValue(_ cell: QChatSwitchCell) { - print("did change switch value : ", cell) - if let key = cell.model?.permissionKey, - let value = cell.model?.permission?.value(forKey: key) as? String, - let type = ChatPermissionType(rawValue: value) { - updatePermission(type, cell.qSwitch.isOn) { success in - if success == false { - cell.qSwitch.isOn = !cell.qSwitch.isOn - } + + // MARK: UITableViewDelegate, UITableViewDataSource, QChatTextEditCellDelegate, ViewModelDelegate, QChatSwitchCellDelegate + + func didChangeSwitchValue(_ cell: QChatSwitchCell) { + print("did change switch value : ", cell) + if let key = cell.model?.permissionKey, + let value = cell.model?.permission?.value(forKey: key) as? String, + let type = ChatPermissionType(rawValue: value) { + updatePermission(type, cell.qSwitch.isOn) { success in + if success == false { + cell.qSwitch.isOn = !cell.qSwitch.isOn } } } + } + + public func dataDidChange() { + tableView.reloadData() + } + + public func dataDidError(_ error: Error) { + showToast(error.localizedDescription) + } - public func dataDidChange() { - tableView.reloadData() + func textDidChange(_ textField: UITextField) { + if let text = textField.text { + serverName = text } + } - public func dataDidError(_ error: Error) { - showToast(error.localizedDescription) + public func numberOfSections(in tableView: UITableView) -> Int { + 5 + } + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if section == 0 { + return 1 + } + if section == 1 { + if let type = idGroup?.role?.type, type == .everyone { + return 0 + } + return 1 + } + if section == 2 { + return viewmodel.commons.count + } + if section == 3 { + return viewmodel.messages.count } + if section == 4 { + return viewmodel.members.count + } + return 0 + } - func textDidChange(_ textField: UITextField) { - if let text = textField.text { - serverName = text + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if indexPath.section == 0 { + let cell: QChatTextEditCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatTextEditCell.self)", + for: indexPath + ) as! QChatTextEditCell + if serverName.count > 0 { + cell.textFied.text = serverName + } else { + cell.textFied.text = nil } + cell.textFied.placeholder = localizable("qchat_please_input_role_name") + cell.delegate = self + cell.limit = 20 + cell.cornerType = CornerType.topLeft.union(CornerType.topRight) + .union(CornerType.bottomLeft).union(CornerType.bottomRight) + return cell } - public func numberOfSections(in tableView: UITableView) -> Int { - 5 + if indexPath.section == 1 { + let cell: QChatTextArrowCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatTextArrowCell.self)", + for: indexPath + ) as! QChatTextArrowCell + cell.titleLabel.text = localizable("qchat_member") + cell.detailLabel.text = "\(idGroup?.role?.memberCount ?? 0)" + cell.cornerType = CornerType.topLeft.union(CornerType.topRight) + .union(CornerType.bottomLeft).union(CornerType.bottomRight) + return cell } - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if section == 0 { - return 1 - } - if section == 1 { - if let type = idGroup?.role?.type, type == .everyone { - return 0 - } - return 1 - } - if section == 2 { - return viewmodel.commons.count - } - if section == 3 { - return viewmodel.messages.count - } - if section == 4 { - return viewmodel.members.count + if indexPath.section == 2 || indexPath.section == 3 || indexPath.section == 4 { + let cell: QChatSwitchCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatSwitchCell.self)", + for: indexPath + ) as! QChatSwitchCell + var model: PermissionCellModel? + if indexPath.section == 2 { + model = viewmodel.commons[indexPath.row] + } else if indexPath.section == 3 { + model = viewmodel.messages[indexPath.row] + } else if indexPath.section == 4 { + model = viewmodel.members[indexPath.row] } - return 0 + cell.delegate = self + cell.model = model + return cell } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if indexPath.section == 0 { - let cell: QChatTextEditCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatTextEditCell.self)", - for: indexPath - ) as! QChatTextEditCell - if serverName.count > 0 { - cell.textFied.text = serverName - } else { - cell.textFied.text = nil - } - cell.textFied.placeholder = localizable("qchat_please_input_role_name") - cell.delegate = self - cell.limit = 20 - cell.cornerType = CornerType.topLeft.union(CornerType.topRight) - .union(CornerType.bottomLeft).union(CornerType.bottomRight) - return cell - } + return UITableViewCell() + } - if indexPath.section == 1 { - let cell: QChatTextArrowCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatTextArrowCell.self)", - for: indexPath - ) as! QChatTextArrowCell - cell.titleLabel.text = localizable("qchat_member") - cell.detailLabel.text = "\(idGroup?.role?.memberCount ?? 0)" - cell.cornerType = CornerType.topLeft.union(CornerType.topRight) - .union(CornerType.bottomLeft).union(CornerType.bottomRight) - return cell - } + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + 50 + } - if indexPath.section == 2 || indexPath.section == 3 || indexPath.section == 4 { - let cell: QChatSwitchCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatSwitchCell.self)", - for: indexPath - ) as! QChatSwitchCell - var model: PermissionCellModel? - if indexPath.section == 2 { - model = viewmodel.commons[indexPath.row] - } else if indexPath.section == 3 { - model = viewmodel.messages[indexPath.row] - } else if indexPath.section == 4 { - model = viewmodel.members[indexPath.row] - } - cell.delegate = self - cell.model = model - return cell + public func tableView(_ tableView: UITableView, + viewForHeaderInSection section: Int) -> UIView? { + let header = QChatHeaderView() + switch section { + case 0: + header.titleLabel.text = localizable("qchat_group_name") + case 1: + if let type = idGroup?.role?.type, type == .everyone { + return nil } - - return UITableViewCell() + header.titleLabel.text = localizable("qchat_manager_member") + case 2: + header.titleLabel.text = localizable("qchat_common_permission") + case 3: + header.titleLabel.text = localizable("qchat_message_permission") + case 4: + header.titleLabel.text = localizable("qchat_member_permission") + default: + break } + return header + } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - 50 + public func tableView(_ tableView: UITableView, + heightForHeaderInSection section: Int) -> CGFloat { + if let type = idGroup?.role?.type, type == .everyone, section == 1 { + return 0 } + return 40 + } - public func tableView(_ tableView: UITableView, - viewForHeaderInSection section: Int) -> UIView? { - let header = QChatHeaderView() - switch section { - case 0: - header.titleLabel.text = localizable("qchat_group_name") - case 1: - if let type = idGroup?.role?.type, type == .everyone { - return nil + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.section == 1 { + let memberManager = QChatMemberManagerController() + memberManager.serverId = idGroup?.role?.serverId + memberManager.roleId = idGroup?.role?.roleId + memberManager.memberCount = idGroup?.role?.memberCount ?? 0 + weak var weakSelf = self + memberManager.countChangeBlock = { count in + weakSelf?.idGroup?.role?.memberCount = count + if let role = weakSelf?.idGroup?.role, let block = weakSelf?.completion { + block(role) } - header.titleLabel.text = localizable("qchat_manager_member") - case 2: - header.titleLabel.text = localizable("qchat_common_permission") - case 3: - header.titleLabel.text = localizable("qchat_message_permission") - case 4: - header.titleLabel.text = localizable("qchat_member_permission") - default: - break + tableView.reloadData() } - return header + navigationController?.pushViewController(memberManager, animated: true) } + } - public func tableView(_ tableView: UITableView, - heightForHeaderInSection section: Int) -> CGFloat { - if let type = idGroup?.role?.type, type == .everyone, section == 1 { - return 0 - } - return 40 + // MAKR: objc 方法 + @objc func savePermission() { + if serverName.count <= 0 { + showToast(localizable("qchat_please_input_role_name")) + return } - - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if indexPath.section == 1 { - let memberManager = QChatMemberManagerController() - memberManager.serverId = idGroup?.role?.serverId - memberManager.roleId = idGroup?.role?.roleId - memberManager.memberCount = idGroup?.role?.memberCount ?? 0 - weak var weakSelf = self - memberManager.countChangeBlock = { count in - weakSelf?.idGroup?.role?.memberCount = count - if let role = weakSelf?.idGroup?.role, let block = weakSelf?.completion { - block(role) - } - tableView.reloadData() - } - navigationController?.pushViewController(memberManager, animated: true) - } + var param = UpdateServerRoleParam() + param.serverId = idGroup?.role?.serverId + if let rid = idGroup?.role?.roleId { + param.roleId = UInt64(rid) } - - //MAKR: objc 方法 - @objc func savePermission() { - if serverName.count <= 0 { - showToast(localizable("qchat_please_input_role_name")) - return - } - var param = UpdateServerRoleParam() - param.serverId = idGroup?.role?.serverId - if let rid = idGroup?.role?.roleId { - param.roleId = UInt64(rid) - } - param.name = serverName - - /* 批量逻辑,暂时不用 - let permissions = viewmodel.permission.getChangePermission() - var commonds = [StatusInfo]() - - permissions.forEach { (type: ChatPermissionType, value: Bool) in - var info = StatusInfo() - info.permissionType = type - if value == true { - info.status = .Allow - }else { - info.status = .Deny - } - commonds.append(info) - } - if commonds.count > 0 { - print("commonds : ", commonds) - print("commonds count :", commonds.count) - param.commands = commonds - } */ + param.name = serverName + + /* 批量逻辑,暂时不用 + let permissions = viewmodel.permission.getChangePermission() + var commonds = [StatusInfo]() + + permissions.forEach { (type: ChatPermissionType, value: Bool) in + var info = StatusInfo() + info.permissionType = type + if value == true { + info.status = .Allow + }else { + info.status = .Deny + } + commonds.append(info) + } + if commonds.count > 0 { + print("commonds : ", commonds) + print("commonds count :", commonds.count) + param.commands = commonds + } */ - weak var weakSelf = self - viewmodel.repo.updateRole(param) { error, role in - if let err = error { - weakSelf?.showToast(err.localizedDescription) - } else { - if let block = weakSelf?.completion { - block(role) - } - weakSelf?.showToastInWindow(localizable("update_channel_suscess")) - weakSelf?.navigationController?.popViewController(animated: true) + weak var weakSelf = self + viewmodel.repo.updateRole(param) { error, role in + if let err = error { + weakSelf?.showToast(err.localizedDescription) + } else { + if let block = weakSelf?.completion { + block(role) } + weakSelf?.showToastInWindow(localizable("update_channel_suscess")) + weakSelf?.navigationController?.popViewController(animated: true) } } + } } - - extension QChatPermissionViewController { func updatePermission(_ type: ChatPermissionType, _ open: Bool, _ completion: @escaping (Bool) -> Void) { @@ -305,6 +305,4 @@ extension QChatPermissionViewController { } } } - - } diff --git a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatServerSettingViewController.swift b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatServerSettingViewController.swift index 5af9670b..a0bc9f63 100644 --- a/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatServerSettingViewController.swift +++ b/NEQChatUIKit/NEKitQChatUI/Classes/Server/ViewController/QChatServerSettingViewController.swift @@ -10,7 +10,8 @@ import NIMSDK typealias SaveSuccessBlock = (_ server: QChatServer?) -> Void -public class QChatServerSettingViewController: NEBaseTableViewController,UITableViewDelegate, UITableViewDataSource,UITextFieldDelegate,UINavigationControllerDelegate { +public class QChatServerSettingViewController: NEBaseTableViewController, UITableViewDelegate, + UITableViewDataSource, UITextFieldDelegate, UINavigationControllerDelegate { let viewModel = SettingViewModel() var server: QChatServer? @@ -173,7 +174,7 @@ public class QChatServerSettingViewController: NEBaseTableViewController,UITable serverNameInput.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: 38), serverNameInput.heightAnchor.constraint(equalToConstant: 50), ]) - serverNameInput.placeholder = "输入名称" + serverNameInput.placeholder = localizable("enter_name") serverNameInput.tag = 50 if let name = server?.name { serverNameInput.text = name @@ -241,253 +242,252 @@ public class QChatServerSettingViewController: NEBaseTableViewController,UITable textField.delegate = self return textField } - - // MARK: action - @objc func cameraClick() { - // print("camera click") - showBottomAlert(self) - } - @objc func saveClick() { - print("save click") + // MARK: action - var name = "" + @objc func cameraClick() { + // print("camera click") + showBottomAlert(self) + } - if let currentName = serverNameInput.text, currentName.count > 0 { - name = currentName - } else if let originServerName = server?.name, originServerName.count > 0 { - name = originServerName - } + @objc func saveClick() { + print("save click") - if name.count <= 0 { - showToast(localizable("qchat_not_empty_servername")) - return - } + var name = "" - if let icon = headerImageUrl { - server?.icon = icon - } + if let currentName = serverNameInput.text, currentName.count > 0 { + name = currentName + } else if let originServerName = server?.name, originServerName.count > 0 { + name = originServerName + } - var serverParam = UpdateServerParam(name: name, icon: headerImageUrl) + if name.count <= 0 { + showToast(localizable("qchat_not_empty_servername")) + return + } - guard let sid = server?.serverId else { - showToast("服务器id不能为空") - return - } - serverParam.serverId = sid + if let icon = headerImageUrl { + server?.icon = icon + } - if let topic = serverThemeInput.text, topic.count > 0 { - serverParam.custom = getJSONStringFromDictionary(["topic": topic]) + var serverParam = UpdateServerParam(name: name, icon: headerImageUrl) + + guard let sid = server?.serverId else { + showToast(localizable("serverId_notbe_empty")) + return + } + serverParam.serverId = sid + + if let topic = serverThemeInput.text, topic.count > 0 { + serverParam.custom = getJSONStringFromDictionary(["topic": topic]) + } + weak var weakSelf = self + + view.makeToastActivity(.center) + print("update param : ", serverParam) + QChatServerProvider.shared.updateServer(serverParam) { error in + print("update finish : ", error as Any) + weakSelf?.view.hideToastActivity() + if let err = error { + weakSelf?.showToast(err.localizedDescription) + } else { + weakSelf?.navigationController?.popViewController(animated: true) } - weak var weakSelf = self + } + } + func leaveServer() { + if let serverid = server?.serverId { + weak var weakSelf = self view.makeToastActivity(.center) - print("update param : ", serverParam) - QChatServerProvider.shared.updateServer(serverParam) { error in - print("update finish : ", error as Any) + let className = className() + viewModel.repo.leaveServer(serverid) { error in weakSelf?.view.hideToastActivity() if let err = error { - weakSelf?.showToast(err.localizedDescription) + NELog.errorLog(className, desc: "leave server error : \(err)") + weakSelf?.view.makeToast(err.localizedDescription) } else { weakSelf?.navigationController?.popViewController(animated: true) } } } + } - func leaveServer() { - if let serverid = server?.serverId { - weak var weakSelf = self - view.makeToastActivity(.center) - let className = className() - viewModel.repo.leaveServer(serverid) { error in - weakSelf?.view.hideToastActivity() - if let err = error { - NELog.errorLog(className, desc: "leave server error : \(err)") - weakSelf?.view.makeToast(err.localizedDescription) - } else { - weakSelf?.navigationController?.popViewController(animated: true) - } + func deleteServer() { + if let serverid = server?.serverId { + weak var weakSelf = self + view.makeToastActivity(.center) + QChatServerProvider.shared.deleteServer(serverid) { error in + print("delete result : ", error as Any) + weakSelf?.view.hideToastActivity() + if let err = error { + weakSelf?.view.makeToast(err.localizedDescription) + } else { + weakSelf?.navigationController?.popViewController(animated: true) } } } + } - func deleteServer() { - if let serverid = server?.serverId { - weak var weakSelf = self - view.makeToastActivity(.center) - QChatServerProvider.shared.deleteServer(serverid) { error in - print("delete result : ", error as Any) - weakSelf?.view.hideToastActivity() - if let err = error { - weakSelf?.view.makeToast(err.localizedDescription) - } else { - weakSelf?.navigationController?.popViewController(animated: true) - } - } - } - } - //MARK: UITableViewDelegate, UITableViewDataSource,UITextFieldDelegate - - public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, - replacementString string: String) -> Bool { - let l = textField.tag - let text = "\(textField.text ?? "")\(string)" - print("count : ", text.count) - if text.count > l { - return false - } - return true - } + // MARK: UITableViewDelegate, UITableViewDataSource,UITextFieldDelegate - public func numberOfSections(in tableView: UITableView) -> Int { - 2 + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, + replacementString string: String) -> Bool { + let l = textField.tag + let text = "\(textField.text ?? "")\(string)" + print("count : ", text.count) + if text.count > l { + return false } + return true + } - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - print("count section : ", section) - if section == 0 { - return viewModel.permissions.count - } else if section == 1 { - return 1 - } - return 0 - } + public func numberOfSections(in tableView: UITableView) -> Int { + 2 + } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if indexPath.section == 0 { - let textCell: QChatTextArrowCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatTextArrowCell.self)", - for: indexPath - ) as! QChatTextArrowCell - let model = viewModel.permissions[indexPath.row] - textCell.titleLabel.text = model.title - textCell.backgroundColor = .clear - textCell.cornerType = model.cornerType - return textCell - } else if indexPath.section == 1 { - let destructiveCell: QChatDestructiveCell = tableView.dequeueReusableCell( - withIdentifier: "\(QChatDestructiveCell.self)", - for: indexPath - ) as! QChatDestructiveCell - - destructiveCell.redTextLabel - .text = isMyServer() ? localizable("qchat_delete_server") : - localizable("qchat_leave_server") - destructiveCell.cornerType = CornerType.bottomLeft.union(CornerType.bottomRight) - .union(CornerType.topLeft).union(CornerType.topRight) - return destructiveCell - } + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + print("count section : ", section) + if section == 0 { + return viewModel.permissions.count + } else if section == 1 { + return 1 + } + return 0 + } - return UITableViewCell() + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if indexPath.section == 0 { + let textCell: QChatTextArrowCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatTextArrowCell.self)", + for: indexPath + ) as! QChatTextArrowCell + let model = viewModel.permissions[indexPath.row] + textCell.titleLabel.text = model.title + textCell.backgroundColor = .clear + textCell.cornerType = model.cornerType + return textCell + } else if indexPath.section == 1 { + let destructiveCell: QChatDestructiveCell = tableView.dequeueReusableCell( + withIdentifier: "\(QChatDestructiveCell.self)", + for: indexPath + ) as! QChatDestructiveCell + + destructiveCell.redTextLabel + .text = isMyServer() ? localizable("qchat_delete_server") : + localizable("qchat_leave_server") + destructiveCell.cornerType = CornerType.bottomLeft.union(CornerType.bottomRight) + .union(CornerType.topLeft).union(CornerType.topRight) + return destructiveCell } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if indexPath.section == 0 { - if indexPath.row == 1 { - let idGroupController = QChatIdGroupViewController() - idGroupController.serverid = server?.serverId - if let owner = server?.owner, owner == IMKitLoginManager.instance.imAccid { - idGroupController.isOwner = true - } - navigationController?.pushViewController(idGroupController, animated: true) + return UITableViewCell() + } - } else if indexPath.row == 0 { - let memberCtrl = MemberListViewController() - memberCtrl.serverId = server?.serverId - navigationController?.pushViewController(memberCtrl, animated: true) + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.section == 0 { + if indexPath.row == 1 { + let idGroupController = QChatIdGroupViewController() + idGroupController.serverid = server?.serverId + if let owner = server?.owner, owner == IMKitEngine.instance.imAccid { + idGroupController.isOwner = true } + navigationController?.pushViewController(idGroupController, animated: true) - } else if indexPath.section == 1 { - print("click delete") - weak var weakSelf = self - if isMyServer() == true { - showAlert(message: "确定删除当前服务器?") { - weakSelf?.deleteServer() - } - } else { - showAlert(message: "确定退出当前服务器?") { - weakSelf?.leaveServer() - } - } + } else if indexPath.row == 0 { + let memberCtrl = MemberListViewController() + memberCtrl.serverId = server?.serverId + navigationController?.pushViewController(memberCtrl, animated: true) } - } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - if indexPath.section == 0 { - return 48 - } else if indexPath.section == 1 { - return 40 + } else if indexPath.section == 1 { + print("click delete") + weak var weakSelf = self + if isMyServer() == true { + showAlert(message: localizable("sure_delete_server")) { + weakSelf?.deleteServer() + } + } else { + showAlert(message: localizable("sure_exit_server")) { + weakSelf?.leaveServer() + } } - return 0 } + } - public func tableView(_ tableView: UITableView, - viewForHeaderInSection section: Int) -> UIView? { - if section == 1 { - return UIView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 24)) - } - return nil + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + if indexPath.section == 0 { + return 48 + } else if indexPath.section == 1 { + return 40 } + return 0 + } - public func tableView(_ tableView: UITableView, - heightForHeaderInSection section: Int) -> CGFloat { - if section == 1 { - return 24 - } - return 0 + public func tableView(_ tableView: UITableView, + viewForHeaderInSection section: Int) -> UIView? { + if section == 1 { + return UIView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 24)) } + return nil + } - public func tableView(_ tableView: UITableView, - heightForFooterInSection section: Int) -> CGFloat { - 0 + public func tableView(_ tableView: UITableView, + heightForHeaderInSection section: Int) -> CGFloat { + if section == 1 { + return 24 } + return 0 + } - func isMyServer() -> Bool { - if let owner = server?.owner { - let accid = IMKitLoginManager.instance.imAccid - if owner == accid { - return true - } + public func tableView(_ tableView: UITableView, + heightForFooterInSection section: Int) -> CGFloat { + 0 + } + + func isMyServer() -> Bool { + if let owner = server?.owner { + let accid = IMKitEngine.instance.imAccid + if owner == accid { + return true } - return false - } - - //UINavigationControllerDelegate - func imagePickerController(_ picker: UIImagePickerController, - didFinishPickingMediaWithInfo info: [UIImagePickerController - .InfoKey: Any]) { - let image: UIImage = info[UIImagePickerController.InfoKey.editedImage] as! UIImage - uploadHeadImage(image: image) - picker.dismiss(animated: true, completion: nil) } + return false + } - public func uploadHeadImage(image: UIImage) { - view.makeToastActivity(.center) - if let imageData = image.jpegData(compressionQuality: 0.6) as NSData? { - let filePath = NSHomeDirectory().appending("/Documents/") - .appending(IMKitLoginManager.instance.imAccid) - let succcess = imageData.write(toFile: filePath, atomically: true) - weak var weakSelf = self - if succcess { - NIMSDK.shared().resourceManager - .upload(filePath, progress: nil) { urlString, error in - if error == nil { - // 显示设置的照片 - weakSelf?.headerImage?.image = image - weakSelf?.headerImageUrl = urlString - weakSelf?.headerImage?.titleLabel.isHidden = true - print("upload image success") - } else { - print("upload image failed,error = \(error!)") - } - weakSelf?.view.hideToastActivity() + // UINavigationControllerDelegate + func imagePickerController(_ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController + .InfoKey: Any]) { + let image: UIImage = info[UIImagePickerController.InfoKey.editedImage] as! UIImage + uploadHeadImage(image: image) + picker.dismiss(animated: true, completion: nil) + } + + public func uploadHeadImage(image: UIImage) { + view.makeToastActivity(.center) + if let imageData = image.jpegData(compressionQuality: 0.6) as NSData? { + let filePath = NSHomeDirectory().appending("/Documents/") + .appending(IMKitEngine.instance.imAccid) + let succcess = imageData.write(toFile: filePath, atomically: true) + weak var weakSelf = self + if succcess { + NIMSDK.shared().resourceManager + .upload(filePath, progress: nil) { urlString, error in + if error == nil { + // 显示设置的照片 + weakSelf?.headerImage?.image = image + weakSelf?.headerImageUrl = urlString + weakSelf?.headerImage?.titleLabel.isHidden = true + print("upload image success") + } else { + print("upload image failed,error = \(error!)") } - } + weakSelf?.view.hideToastActivity() + } } } + } } - - - diff --git a/NETeamUIKit/NEKitTeamUI.podspec b/NETeamUIKit/NEKitTeamUI.podspec index c82b1a6a..5de7300c 100644 --- a/NETeamUIKit/NEKitTeamUI.podspec +++ b/NETeamUIKit/NEKitTeamUI.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'NEKitTeamUI' - s.version = '9.2.7' + s.version = '9.2.8' s.summary = 'Netease XKit' # This description is used to generate tags and improve search results. @@ -41,7 +41,6 @@ TODO: Add long description of the pod here. # s.public_header_files = 'Pod/Classes/**/*.h' # s.frameworks = 'UIKit', 'MapKit' - # s.dependency 'AFNetworking', '~> 2.3' s.dependency 'NECommonUIKit' s.dependency 'NETeamKit' end diff --git a/NETeamUIKit/NEKitTeamUI/Assets/en.lproj/Localizable.strings b/NETeamUIKit/NEKitTeamUI/Assets/en.lproj/Localizable.strings index 840c4890..ec826c22 100644 --- a/NETeamUIKit/NEKitTeamUI/Assets/en.lproj/Localizable.strings +++ b/NETeamUIKit/NEKitTeamUI/Assets/en.lproj/Localizable.strings @@ -3,49 +3,48 @@ // Use of this source code is governed by a MIT license that can be // found in the LICENSE file. -"mark"="标记"; -"history"="历史记录"; -"message_remind"="开启消息提醒"; -"session_set_top"="聊天置顶"; - -"team_nick"="我在群里的昵称"; -"team_no_speak"="群禁言"; - -"invite_permission"="邀请他人权限"; -"modify_team_info_permission"="群资料修改权限"; -"agree"="是否需要被邀请者同意"; - -"setting"="设置"; - -"group_memmber"="群成员"; -"discuss_mebmer"="讨论组成员"; - -"dismiss_team"="解散群聊"; -"leave_team"="退出群聊"; -"leave_discuss"="退出讨论组"; - -"team_header"="群头像"; -"team_name"="群名称"; -"team_intr"="群介绍"; - -"default_icon"="选择默认图标"; - -"senior_team"="高级群"; -"normal_team"="讨论组"; - -"create_senior_team_noti"="成功创建高级群"; - -"team_all"="所有人"; -"team_owner"="群主"; - - - - - - - - - - - - +"mark"="pin"; +"history"="History"; +"message_remind"="Open message remind"; +"session_set_top"="Sticky on Top"; +"team_nick"="My Alias in Group"; +"team_no_speak"="Mute"; +"invite_permission"="Invite Permission"; +"modify_team_info_permission"="Group info Permission"; +"agree"="是否需要被邀请者同意";//后期能力删除 +"setting"="Setting"; +"group_memmber"="Group Member"; +"discuss_mebmer"="Temp Group Member"; +"dismiss_team"="Disband Group"; +"leave_team"="Leave Group"; +"leave_discuss"="Leave Temp Group"; +"team_header"=" Group Avator"; +"team_name"="Group Name"; +"team_intr"="Group Info"; +"default_icon"="Select Default icon"; +"senior_team"="Group"; +"normal_team"="Temp Group"; +"create_senior_team_noti"=" Create Group successfully"; +"team_all"="all"; +"team_owner"="Group Admin"; + + +"modify_headImage"="Modify Avatar"; +"save"="Save"; +"historical_record"="History"; +"search"="Search"; +"no_search_results"="No content"; +"discuss_info"="Temp Group Info"; +"group_info"="Group Info"; +"discuss_introduce"="Temp Group Introduce"; +"search_friend"="Search Contact"; +"discuss_name"="Temp Group Name"; +"dissolute_team_chat"="Wether to disband Group"; +"quit_team_chat"="Wether to leave Group"; +"quit_discuss_chat"="Wether to leave Temp Group"; +"cancel"="cancel"; +"remind"="remind"; +"discuss_avatar"="Temp Group Avator"; +"discuss_name"="Temp Group Name"; +"discuss_intro"="Temp Group Introduce"; +"invite_has_send"="Invitation sent"; diff --git a/NETeamUIKit/NEKitTeamUI/Assets/zh-Hans.lproj/Localizable.strings b/NETeamUIKit/NEKitTeamUI/Assets/zh-Hans.lproj/Localizable.strings index cf30978c..a2f371d5 100644 --- a/NETeamUIKit/NEKitTeamUI/Assets/zh-Hans.lproj/Localizable.strings +++ b/NETeamUIKit/NEKitTeamUI/Assets/zh-Hans.lproj/Localizable.strings @@ -7,33 +7,44 @@ "history"="历史记录"; "message_remind"="开启消息提醒"; "session_set_top"="聊天置顶"; - "team_nick"="我在群里的昵称"; "team_no_speak"="群禁言"; - "invite_permission"="邀请他人权限"; "modify_team_info_permission"="群资料修改权限"; "agree"="是否需要被邀请者同意"; - "setting"="设置"; - "group_memmber"="群成员"; "discuss_mebmer"="讨论组成员"; - "dismiss_team"="解散群聊"; "leave_team"="退出群聊"; "leave_discuss"="退出讨论组"; - "team_header"="群头像"; "team_name"="群名称"; "team_intr"="群介绍"; - "default_icon"="选择默认图标"; - "senior_team"="高级群"; "normal_team"="讨论组"; - "create_senior_team_noti"="成功创建高级群"; - "team_all"="所有人"; "team_owner"="群主"; + + +"modify_headImage"="修改头像"; +"save"="保存"; +"historical_record"="历史记录"; +"search"="搜索"; +"no_search_results"="暂无搜索结果"; +"discuss_info"="讨论组信息"; +"group_info"="群信息"; +"discuss_introduce"="讨论组介绍"; +"search_friend"="搜索好友"; +"discuss_name"="讨论组名称"; +"dissolute_team_chat"="是否解散群聊?"; +"quit_team_chat"="是否退出群聊?"; +"quit_discuss_chat"="是否退出讨论组?"; +"cancel"="取消"; +"remind"="提示"; +"discuss_avatar"="讨论组头像"; +"discuss_name"="讨论组名称"; +"discuss_intro"="讨论组介绍"; +"invite_has_send"="邀请已发送"; diff --git a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamAvatarViewController.swift b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamAvatarViewController.swift index 31c112b3..b3c9b145 100644 --- a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamAvatarViewController.swift +++ b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamAvatarViewController.swift @@ -8,7 +8,8 @@ import NEKitCommonUI import NIMSDK import NEKitTeam -public class TeamAvatarViewController: NEBaseViewController,UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout,UINavigationControllerDelegate { +public class TeamAvatarViewController: NEBaseViewController, UICollectionViewDelegate, + UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UINavigationControllerDelegate { typealias SaveCompletion = () -> Void var block: SaveCompletion? @@ -49,8 +50,8 @@ public class TeamAvatarViewController: NEBaseViewController,UICollectionViewDele } func setupUI() { - title = "修改头像" - addRightAction("保存", #selector(savePhoto), self) + title = localizable("modify_headImage") + addRightAction(localizable("save"), #selector(savePhoto), self) view.backgroundColor = NEConstant.hexRGB(0xF1F1F6) let headerBack = UIView() @@ -166,7 +167,7 @@ public class TeamAvatarViewController: NEBaseViewController,UICollectionViewDele if let type = team?.type, type == .normal { return true } - if let ownerId = team?.owner, IMKitLoginManager.instance.isMySelf(ownerId) { + if let ownerId = team?.owner, IMKitEngine.instance.isMySelf(ownerId) { return true } if let mode = team?.updateInfoMode, mode == .all { @@ -184,108 +185,108 @@ public class TeamAvatarViewController: NEBaseViewController,UICollectionViewDele // Pass the selected object to the new view controller. } */ - //MARK: objc 方法 - - @objc func uploadPhoto() { - print("upload photo") - showBottomAlert(self) - } - @objc func savePhoto() { - print("save photo") - if let tid = team?.teamId { - view.makeToastActivity(.center) - weak var weakSelf = self - - repo.fetchNOSURL(url: headerUrl) { error, urlStr in - if error == nil { - weakSelf?.repo.updateTeamIcon(urlStr ?? "", tid) { error in - weakSelf?.view.hideToastActivity() - if let err = error { - weakSelf?.showToast(err.localizedDescription) - } else { - weakSelf?.team?.avatarUrl = weakSelf?.headerUrl - if let completion = weakSelf?.block { - completion() - } - weakSelf?.navigationController?.popViewController(animated: true) + // MARK: objc 方法 + + @objc func uploadPhoto() { + print("upload photo") + showBottomAlert(self) + } + + @objc func savePhoto() { + print("save photo") + if let tid = team?.teamId { + view.makeToastActivity(.center) + weak var weakSelf = self + + repo.fetchNOSURL(url: headerUrl) { error, urlStr in + if error == nil { + weakSelf?.repo.updateTeamIcon(urlStr ?? "", tid) { error in + weakSelf?.view.hideToastActivity() + if let err = error { + weakSelf?.showToast(err.localizedDescription) + } else { + weakSelf?.team?.avatarUrl = weakSelf?.headerUrl + if let completion = weakSelf?.block { + completion() } + weakSelf?.navigationController?.popViewController(animated: true) } } } } } - - //MAKR: UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout - public func collectionView(_ collectionView: UICollectionView, - numberOfItemsInSection section: Int) -> Int { - 5 - } + } - public func collectionView(_ collectionView: UICollectionView, - cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - if let cell = collectionView.dequeueReusableCell( - withReuseIdentifier: "\(TeamDefaultIconCell.self)", - for: indexPath - ) as? TeamDefaultIconCell { - cell.iconImage.image = coreLoader.loadImage("icon_\(indexPath.row)") + // MAKR: UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout + public func collectionView(_ collectionView: UICollectionView, + numberOfItemsInSection section: Int) -> Int { + 5 + } - return cell - } - return UICollectionViewCell() - } + public func collectionView(_ collectionView: UICollectionView, + cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + if let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: "\(TeamDefaultIconCell.self)", + for: indexPath + ) as? TeamDefaultIconCell { + cell.iconImage.image = coreLoader.loadImage("icon_\(indexPath.row)") - public func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { - let space = (view.width - 297.0) / 4.0 - print("mini inter : ", space) - return space + return cell } + return UICollectionViewCell() + } - public func collectionView(_ collectionView: UICollectionView, - didSelectItemAt indexPath: IndexPath) { - if TeamRouter.iconUrls.count > indexPath.row { - headerUrl = TeamRouter.iconUrls[indexPath.row] - // headerView.image = coreLoader.loadImage("icon_\(indexPath.row)") - headerView.sd_setImage(with: URL(string: headerUrl), completed: nil) - } - } - - //MARK: UINavigationControllerDelegate - - func imagePickerController(_ picker: UIImagePickerController, - didFinishPickingMediaWithInfo info: [UIImagePickerController - .InfoKey: Any]) { - let image: UIImage = info[UIImagePickerController.InfoKey.editedImage] as! UIImage - uploadHeadImage(image: image) - picker.dismiss(animated: true, completion: nil) + public func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + let space = (view.width - 297.0) / 4.0 + print("mini inter : ", space) + return space + } + + public func collectionView(_ collectionView: UICollectionView, + didSelectItemAt indexPath: IndexPath) { + if TeamRouter.iconUrls.count > indexPath.row { + headerUrl = TeamRouter.iconUrls[indexPath.row] + // headerView.image = coreLoader.loadImage("icon_\(indexPath.row)") + headerView.sd_setImage(with: URL(string: headerUrl), completed: nil) } + } - public func uploadHeadImage(image: UIImage) { - view.makeToastActivity(.center) - if let imageData = image.jpegData(compressionQuality: 0.6) as NSData? { - let filePath = NSHomeDirectory().appending("/Documents/") - .appending(IMKitLoginManager.instance.imAccid) - let succcess = imageData.write(toFile: filePath, atomically: true) - weak var weakSelf = self - if succcess { - NIMSDK.shared().resourceManager - .upload(filePath, progress: nil) { urlString, error in - if error == nil { - // 显示设置的照片 - weakSelf?.headerView.image = image - if let url = urlString { - weakSelf?.headerUrl = url - } - print("upload image success") - } else { - print("upload image failed,error = \(error!)") + // MARK: UINavigationControllerDelegate + + func imagePickerController(_ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController + .InfoKey: Any]) { + let image: UIImage = info[UIImagePickerController.InfoKey.editedImage] as! UIImage + uploadHeadImage(image: image) + picker.dismiss(animated: true, completion: nil) + } + + public func uploadHeadImage(image: UIImage) { + view.makeToastActivity(.center) + if let imageData = image.jpegData(compressionQuality: 0.6) as NSData? { + let filePath = NSHomeDirectory().appending("/Documents/") + .appending(IMKitEngine.instance.imAccid) + let succcess = imageData.write(toFile: filePath, atomically: true) + weak var weakSelf = self + if succcess { + NIMSDK.shared().resourceManager + .upload(filePath, progress: nil) { urlString, error in + if error == nil { + // 显示设置的照片 + weakSelf?.headerView.image = image + if let url = urlString { + weakSelf?.headerUrl = url } - weakSelf?.view.hideToastActivity() + print("upload image success") + } else { + print("upload image failed,error = \(error!)") } - } + weakSelf?.view.hideToastActivity() + } } } + } } - diff --git a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamHistoryMessageController.swift b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamHistoryMessageController.swift index 4d144958..ec76925e 100644 --- a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamHistoryMessageController.swift +++ b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamHistoryMessageController.swift @@ -6,7 +6,8 @@ import UIKit import NIMSDK -public class TeamHistoryMessageController: NEBaseViewController,UITextFieldDelegate,UITableViewDelegate, UITableViewDataSource { +public class TeamHistoryMessageController: NEBaseViewController, UITextFieldDelegate, + UITableViewDelegate, UITableViewDataSource { private let viewmodel = TeamSettingViewModel() private var teamSession: NIMSession? private var searchStr = "" @@ -58,7 +59,7 @@ public class TeamHistoryMessageController: NEBaseViewController,UITextFieldDeleg } func initialConfig() { - title = "历史记录" + title = localizable("historical_record") } // MARK: lazy method @@ -86,7 +87,7 @@ public class TeamHistoryMessageController: NEBaseViewController,UITextFieldDeleg textField.contentMode = .center textField.leftView = leftImageView textField.leftViewMode = .always - textField.placeholder = localizable("搜索") + textField.placeholder = localizable("search") textField.font = UIFont.systemFont(ofSize: 14) textField.textColor = UIColor.ne_greyText textField.translatesAutoresizingMaskIntoConstraints = false @@ -101,7 +102,11 @@ public class TeamHistoryMessageController: NEBaseViewController,UITextFieldDeleg }() private lazy var emptyView: NEEmptyDataView = { - let view = NEEmptyDataView(imageName: "emptyView", content: "暂无搜索结果", frame: CGRect.zero) + let view = NEEmptyDataView( + imageName: "emptyView", + content: localizable("no_search_results"), + frame: CGRect.zero + ) view.translatesAutoresizingMaskIntoConstraints = false view.isHidden = true return view @@ -116,75 +121,73 @@ public class TeamHistoryMessageController: NEBaseViewController,UITextFieldDeleg tableView.reloadData() } } - - //MARK: UITextFieldDelegate - public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - guard let searchText = textField.text else { - return false - } - if searchText.count <= 0 { - return false - } - guard let session = teamSession else { - return false - } - weak var weakSelf = self - searchStr = searchText - let option = NIMMessageSearchOption() - option.searchContent = searchText - weakSelf?.viewmodel.searchMessages(session, option: option) { error, messages in - if error == nil { - if let msg = messages, msg.count > 0 { - weakSelf?.emptyView.isHidden = true - } else { - weakSelf?.emptyView.isHidden = false - } - weakSelf?.tableView.reloadData() - } else { - NELog.errorLog( - weakSelf?.tag ?? "TeamHistoryMessageController", - desc: "❌searchMessages failed, error = \(error!)" - ) - } - } - return true + // MARK: UITextFieldDelegate + + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + guard let searchText = textField.text else { + return false } - - //MARK: UITableViewDelegate, UITableViewDataSource - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - viewmodel.searchResultInfos?.count ?? 0 + if searchText.count <= 0 { + return false } - - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell( - withIdentifier: "\(NSStringFromClass(HistoryMessageCell.self))", - for: indexPath - ) as! HistoryMessageCell - let cellModel = viewmodel.searchResultInfos?[indexPath.row] - cell.searchText = searchStr - cell.configData(message: cellModel) - return cell + guard let session = teamSession else { + return false } - - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let cellModel = viewmodel.searchResultInfos?[indexPath.row] - if cellModel?.imMessage?.session?.sessionType == .team { - if let sid = cellModel?.imMessage?.session?.sessionId, - let message = cellModel?.imMessage { - let session = NIMSession(sid, type: .team) - Router.shared.use( - PushTeamChatVCRouter, - parameters: ["nav": navigationController as Any, "session": session as Any, - "anchor": message], - closure: nil - ) + weak var weakSelf = self + searchStr = searchText + let option = NIMMessageSearchOption() + option.searchContent = searchText + weakSelf?.viewmodel.searchMessages(session, option: option) { error, messages in + if error == nil { + if let msg = messages, msg.count > 0 { + weakSelf?.emptyView.isHidden = true + } else { + weakSelf?.emptyView.isHidden = false } + weakSelf?.tableView.reloadData() + } else { + NELog.errorLog( + weakSelf?.tag ?? "TeamHistoryMessageController", + desc: "❌searchMessages failed, error = \(error!)" + ) } } -} + return true + } + // MARK: UITableViewDelegate, UITableViewDataSource + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + viewmodel.searchResultInfos?.count ?? 0 + } + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell( + withIdentifier: "\(NSStringFromClass(HistoryMessageCell.self))", + for: indexPath + ) as! HistoryMessageCell + let cellModel = viewmodel.searchResultInfos?[indexPath.row] + cell.searchText = searchStr + cell.configData(message: cellModel) + return cell + } + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let cellModel = viewmodel.searchResultInfos?[indexPath.row] + if cellModel?.imMessage?.session?.sessionType == .team { + if let sid = cellModel?.imMessage?.session?.sessionId, + let message = cellModel?.imMessage { + let session = NIMSession(sid, type: .team) + Router.shared.use( + PushTeamChatVCRouter, + parameters: ["nav": navigationController as Any, "session": session as Any, + "anchor": message], + closure: nil + ) + } + } + } +} diff --git a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamInfoViewController.swift b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamInfoViewController.swift index b8fbfa6e..5e452ca0 100644 --- a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamInfoViewController.swift +++ b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamInfoViewController.swift @@ -6,7 +6,8 @@ import UIKit import NIMSDK -public class TeamInfoViewController: NEBaseViewController,UITableViewDelegate, UITableViewDataSource { +public class TeamInfoViewController: NEBaseViewController, UITableViewDelegate, + UITableViewDataSource { let viewmodel = TeamInfoViewModel() var team: NIMTeam? @@ -36,9 +37,9 @@ public class TeamInfoViewController: NEBaseViewController,UITableViewDelegate, U // Do any additional setup after loading the view. if let type = team?.type, type == .normal { - title = "讨论组信息" + title = localizable("discuss_info") } else { - title = "群信息" + title = localizable("group_info") } viewmodel.getData(team) setupUI() @@ -67,58 +68,58 @@ public class TeamInfoViewController: NEBaseViewController,UITableViewDelegate, U // Pass the selected object to the new view controller. } */ - //MARK: UITableViewDelegate, UITableViewDataSource - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - viewmodel.cellDatas.count - } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let model = viewmodel.cellDatas[indexPath.row] - if let cell = tableView.dequeueReusableCell( - withIdentifier: "\(model.type)", - for: indexPath - ) as? BaseTeamSettingCell { - cell.configure(model) - return cell - } - return UITableViewCell() + // MARK: UITableViewDelegate, UITableViewDataSource + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + viewmodel.cellDatas.count + } + + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let model = viewmodel.cellDatas[indexPath.row] + if let cell = tableView.dequeueReusableCell( + withIdentifier: "\(model.type)", + for: indexPath + ) as? BaseTeamSettingCell { + cell.configure(model) + return cell } + return UITableViewCell() + } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if indexPath.row == 0 { - let avatar = TeamAvatarViewController() - avatar.team = team - weak var weakSelf = self - avatar.block = { - if let t = weakSelf?.team { - weakSelf?.viewmodel.getData(t) - weakSelf?.contentTable.reloadData() - } + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.row == 0 { + let avatar = TeamAvatarViewController() + avatar.team = team + weak var weakSelf = self + avatar.block = { + if let t = weakSelf?.team { + weakSelf?.viewmodel.getData(t) + weakSelf?.contentTable.reloadData() } - navigationController?.pushViewController(avatar, animated: true) - - } else if indexPath.row == 1 { - let nameController = TeamNameViewController() - nameController.team = team - navigationController?.pushViewController(nameController, animated: true) - } else if indexPath.row == 2 { - let intr = TeamIntroduceViewController() - intr.team = team - navigationController?.pushViewController(intr, animated: true) } + navigationController?.pushViewController(avatar, animated: true) + + } else if indexPath.row == 1 { + let nameController = TeamNameViewController() + nameController.team = team + navigationController?.pushViewController(nameController, animated: true) + } else if indexPath.row == 2 { + let intr = TeamIntroduceViewController() + intr.team = team + navigationController?.pushViewController(intr, animated: true) } + } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - let model = viewmodel.cellDatas[indexPath.row] - return model.rowHeight - } + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + let model = viewmodel.cellDatas[indexPath.row] + return model.rowHeight + } - public func tableView(_ tableView: UITableView, - heightForHeaderInSection section: Int) -> CGFloat { - 12.0 - } - + public func tableView(_ tableView: UITableView, + heightForHeaderInSection section: Int) -> CGFloat { + 12.0 + } } - diff --git a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamIntroduceViewController.swift b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamIntroduceViewController.swift index cd5e7473..e0cbcd5a 100644 --- a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamIntroduceViewController.swift +++ b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamIntroduceViewController.swift @@ -8,7 +8,7 @@ import NEKitCommon import NIMSDK import NEKitTeam -public class TeamIntroduceViewController: NEBaseViewController,UITextViewDelegate { +public class TeamIntroduceViewController: NEBaseViewController, UITextViewDelegate { // typealias SaveCompletion = () -> Void // // var block: SaveCompletion? @@ -44,12 +44,12 @@ public class TeamIntroduceViewController: NEBaseViewController,UITextViewDelegat } func setupUI() { - addRightAction("保存", #selector(saveIntr), self) + addRightAction(localizable("save"), #selector(saveIntr), self) if let type = team?.type, type == .advanced { - title = "群介绍" + title = localizable("team_intr") } else { - title = "讨论组介绍" + title = localizable("discuss_introduce") } view.backgroundColor = NEConstant.hexRGB(0xF1F1F6) @@ -127,7 +127,7 @@ public class TeamIntroduceViewController: NEBaseViewController,UITextViewDelegat */ func changePermission() -> Bool { - if let ownerId = team?.owner, IMKitLoginManager.instance.isMySelf(ownerId) { + if let ownerId = team?.owner, IMKitEngine.instance.isMySelf(ownerId) { return true } if let mode = team?.updateInfoMode, mode == .all { @@ -135,49 +135,50 @@ public class TeamIntroduceViewController: NEBaseViewController,UITextViewDelegat } return false } - - @objc func saveIntr() { - textView.resignFirstResponder() - if let teamid = team?.teamId { - let text = textView.text ?? "" - weak var weakSelf = self - view.makeToastActivity(.center) - repo.updateTeamIntroduce(text, teamid) { error in - weakSelf?.view.hideToastActivity() - if let err = error { - weakSelf?.showToast(err.localizedDescription) - } else { - weakSelf?.team?.intro = text - weakSelf?.navigationController?.popViewController(animated: true) - } + + @objc func saveIntr() { + textView.resignFirstResponder() + if let teamid = team?.teamId { + let text = textView.text ?? "" + weak var weakSelf = self + view.makeToastActivity(.center) + repo.updateTeamIntroduce(text, teamid) { error in + weakSelf?.view.hideToastActivity() + if let err = error { + weakSelf?.showToast(err.localizedDescription) + } else { + weakSelf?.team?.intro = text + weakSelf?.navigationController?.popViewController(animated: true) } } } - //MARK: UITextViewDelegate - public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, - replacementText text: String) -> Bool { - let currentText = textView.text ?? "" - guard let stringRange = Range(range, in: currentText) else { return false } - let updatedText = currentText.replacingCharacters(in: stringRange, with: text) - return updatedText.count <= 100 - } + } + + // MARK: UITextViewDelegate - public func textViewDidChange(_ textView: UITextView) { - if var text = textView.text { - if let lang = textView.textInputMode?.primaryLanguage, lang == "zh-Hans", - let selectRange = textView.markedTextRange { - let position = textView.position(from: selectRange.start, offset: 0) - if position == nil { - if text.count > 30 { - text = String(text.prefix(30)) - textView.text = String(text.prefix(30)) - } - countLabel.text = "\(text.count)/100" + public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, + replacementText text: String) -> Bool { + let currentText = textView.text ?? "" + guard let stringRange = Range(range, in: currentText) else { return false } + let updatedText = currentText.replacingCharacters(in: stringRange, with: text) + return updatedText.count <= 100 + } + + public func textViewDidChange(_ textView: UITextView) { + if var text = textView.text { + if let lang = textView.textInputMode?.primaryLanguage, lang == "zh-Hans", + let selectRange = textView.markedTextRange { + let position = textView.position(from: selectRange.start, offset: 0) + if position == nil { + if text.count > 30 { + text = String(text.prefix(30)) + textView.text = String(text.prefix(30)) } - } else { countLabel.text = "\(text.count)/100" } + } else { + countLabel.text = "\(text.count)/100" } } + } } - diff --git a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamMembersController.swift b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamMembersController.swift index 058e85f0..a003f96b 100644 --- a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamMembersController.swift +++ b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamMembersController.swift @@ -8,7 +8,8 @@ import NEKitTeam import NEKitCore // -public class TeamMembersController: NEBaseViewController,UITableViewDelegate, UITableViewDataSource { +public class TeamMembersController: NEBaseViewController, UITableViewDelegate, + UITableViewDataSource { var datas: [TeamMemberInfoModel]? var ownerId: String? @@ -20,7 +21,7 @@ public class TeamMembersController: NEBaseViewController,UITableViewDelegate, UI lazy var searchTextField: UITextField = { let field = UITextField() field.translatesAutoresizingMaskIntoConstraints = false - field.placeholder = "搜索好友" + field.placeholder = localizable("search_friend") field.textColor = .ne_greyText field.font = UIFont.systemFont(ofSize: 14.0) field.backgroundColor = UIColor.ne_backcolor @@ -57,9 +58,9 @@ public class TeamMembersController: NEBaseViewController,UITableViewDelegate, UI func setupUI() { if isSenior { - title = "群成员" + title = localizable("group_memmber") } else { - title = "讨论组成员" + title = localizable("discuss_mebmer") } let back = UIView() @@ -162,63 +163,62 @@ public class TeamMembersController: NEBaseViewController,UITableViewDelegate, UI } contentTable.reloadData() } - func getRealModel(_ index: Int) -> TeamMemberInfoModel? { - if let text = searchTextField.text, text.count > 0 { - return searchDatas[index] - } - return datas?[index] + + func getRealModel(_ index: Int) -> TeamMemberInfoModel? { + if let text = searchTextField.text, text.count > 0 { + return searchDatas[index] } - - + return datas?[index] + } + deinit { NotificationCenter.default.removeObserver(self) } - - - //MARK: UITableViewDelegate, UITableViewDataSource - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if let text = searchTextField.text, text.count > 0 { - return searchDatas.count - } - return datas?.count ?? 0 + // MARK: UITableViewDelegate, UITableViewDataSource + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if let text = searchTextField.text, text.count > 0 { + return searchDatas.count } + return datas?.count ?? 0 + } - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if let cell = tableView.dequeueReusableCell( - withIdentifier: "\(TeamMemberCell.self)", - for: indexPath - ) as? TeamMemberCell { - if let model = getRealModel(indexPath.row) { - cell.configure(model) - cell.ownerLabel.isHidden = !isOwner(model.nimUser?.userId) - } - return cell + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if let cell = tableView.dequeueReusableCell( + withIdentifier: "\(TeamMemberCell.self)", + for: indexPath + ) as? TeamMemberCell { + if let model = getRealModel(indexPath.row) { + cell.configure(model) + cell.ownerLabel.isHidden = !isOwner(model.nimUser?.userId) } - return UITableViewCell() + return cell } + return UITableViewCell() + } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - 62.0 - } + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + 62.0 + } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if let model = getRealModel(indexPath.row), let user = model.nimUser { - if IMKitLoginManager.instance.isMySelf(user.userId) { - Router.shared.use( - MeSettingRouter, - parameters: ["nav": navigationController as Any], - closure: nil - ) - } else { - Router.shared.use( - ContactUserInfoPageRouter, - parameters: ["nav": navigationController as Any, "nim_user": user], - closure: nil - ) - } + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if let model = getRealModel(indexPath.row), let user = model.nimUser { + if IMKitEngine.instance.isMySelf(user.userId) { + Router.shared.use( + MeSettingRouter, + parameters: ["nav": navigationController as Any], + closure: nil + ) + } else { + Router.shared.use( + ContactUserInfoPageRouter, + parameters: ["nav": navigationController as Any, "nim_user": user], + closure: nil + ) } } + } } diff --git a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamNameViewController.swift b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamNameViewController.swift index 2d253601..9dc72afe 100644 --- a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamNameViewController.swift +++ b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamNameViewController.swift @@ -13,7 +13,7 @@ enum ChangeType { case NickName } -public class TeamNameViewController: NEBaseViewController,UITextFieldDelegate { +public class TeamNameViewController: NEBaseViewController, UITextFieldDelegate { var team: NIMTeam? // var user: NIMUser? var type = ChangeType.TeamName @@ -97,13 +97,13 @@ public class TeamNameViewController: NEBaseViewController,UITextFieldDelegate { textField.isEnabled = false } if let teamType = team?.type, teamType == .normal { - title = "讨论组名称" + title = localizable("discuss_name") } else { - title = "群名称" + title = localizable("team_name") } } else if type == .NickName, let n = teamMember?.nickname { - title = "我在群里的昵称" + title = localizable("team_nick") name = n } @@ -121,7 +121,7 @@ public class TeamNameViewController: NEBaseViewController,UITextFieldDelegate { object: textField ) - addRightAction("保存", #selector(saveName), self) + addRightAction(localizable("save"), #selector(saveName), self) } /* @@ -139,7 +139,7 @@ public class TeamNameViewController: NEBaseViewController,UITextFieldDelegate { return true } - if let ownerId = team?.owner, IMKitLoginManager.instance.isMySelf(ownerId) { + if let ownerId = team?.owner, IMKitEngine.instance.isMySelf(ownerId) { return true } if let mode = team?.updateInfoMode, mode == .all { @@ -157,70 +157,69 @@ public class TeamNameViewController: NEBaseViewController,UITextFieldDelegate { rightNavBtn.setTitleColor(NEConstant.hexRGB(0x337EFF), for: .normal) rightNavBtn.isEnabled = true } - - - //MARK: objc 方法 - @objc func textFieldChange() { - if var text = textField.text { - if let lang = textField.textInputMode?.primaryLanguage, lang == "zh-Hans", - let selectRange = textField.markedTextRange { - let position = textField.position(from: selectRange.start, offset: 0) - if position == nil { - if text.count > 30 { - text = String(text.prefix(30)) - textField.text = String(text.prefix(30)) - } - figureTextCount(text) + + // MARK: objc 方法 + + @objc func textFieldChange() { + if var text = textField.text { + if let lang = textField.textInputMode?.primaryLanguage, lang == "zh-Hans", + let selectRange = textField.markedTextRange { + let position = textField.position(from: selectRange.start, offset: 0) + if position == nil { + if text.count > 30 { + text = String(text.prefix(30)) + textField.text = String(text.prefix(30)) } - } else { figureTextCount(text) } + } else { + figureTextCount(text) } } - - @objc func saveName() { - weak var weakSelf = self - textField.resignFirstResponder() - if type == .TeamName, let tid = team?.teamId { - let n = textField.text ?? "" - view.makeToastActivity(.center) - repo.updateTeamName(n, tid) { error in - weakSelf?.view.hideToastActivity() - if let err = error { - weakSelf?.showToast(err.localizedDescription) - } else { - weakSelf?.team?.teamName = n - weakSelf?.navigationController?.popViewController(animated: true) - } + } + + @objc func saveName() { + weak var weakSelf = self + textField.resignFirstResponder() + if type == .TeamName, let tid = team?.teamId { + let n = textField.text ?? "" + view.makeToastActivity(.center) + repo.updateTeamName(n, tid) { error in + weakSelf?.view.hideToastActivity() + if let err = error { + weakSelf?.showToast(err.localizedDescription) + } else { + weakSelf?.team?.teamName = n + weakSelf?.navigationController?.popViewController(animated: true) } - } else if type == .NickName, let tid = team?.teamId, let uid = teamMember?.userId { - let n = textField.text ?? "" - view.makeToastActivity(.center) - repo.updateMemberNick(uid, n, tid) { error in - - weakSelf?.view.hideToastActivity() - if let err = error { - weakSelf?.showToast(err.localizedDescription) - } else { - weakSelf?.navigationController?.popViewController(animated: true) - } + } + } else if type == .NickName, let tid = team?.teamId, let uid = teamMember?.userId { + let n = textField.text ?? "" + view.makeToastActivity(.center) + repo.updateMemberNick(uid, n, tid) { error in + + weakSelf?.view.hideToastActivity() + if let err = error { + weakSelf?.showToast(err.localizedDescription) + } else { + weakSelf?.navigationController?.popViewController(animated: true) } } } - //MAKR: UITextFieldDelegate - public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, - replacementString string: String) -> Bool { - if let text = (textField.text as NSString?)?.replacingCharacters(in: range, with: string), - text.count > 30 { - return false - } - return true + } + + // MAKR: UITextFieldDelegate + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, + replacementString string: String) -> Bool { + if let text = (textField.text as NSString?)?.replacingCharacters(in: range, with: string), + text.count > 30 { + return false } - + return true + } } extension TeamNameViewController { - func figureTextCount(_ text: String) { countLabel.text = "\(text.count)/30" if type == .NickName { @@ -232,6 +231,4 @@ extension TeamNameViewController { disableSubmit() } } - - } diff --git a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamSettingViewController.swift b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamSettingViewController.swift index b170c709..b1d02b2b 100644 --- a/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamSettingViewController.swift +++ b/NETeamUIKit/NEKitTeamUI/Classes/Setting/TeamSettingViewController.swift @@ -8,7 +8,9 @@ import NEKitCommonUI import NEKitCoreIM import NIMSDK -public class TeamSettingViewController: NEBaseViewController,UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout,UITableViewDataSource, UITableViewDelegate { +public class TeamSettingViewController: NEBaseViewController, UICollectionViewDelegate, + UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UITableViewDataSource, + UITableViewDelegate { let viewmodel = TeamSettingViewModel() var teamId: String? @@ -321,189 +323,191 @@ public class TeamSettingViewController: NEBaseViewController,UICollectionViewDel forCellWithReuseIdentifier: "\(TeamUserCell.self)" ) } -//MARK: objc 方法 - @objc func addUser() { - weak var weakSelf = self - Router.shared.register(ContactSelectedUsersRouter) { param in - print("addUser weak self ", weakSelf as Any) - if let accids = param["accids"] as? [String], - let tid = self.viewmodel.teamInfoModel?.team?.teamId, - let beInviteMode = self.viewmodel.teamInfoModel?.team?.beInviteMode, - let type = self.viewmodel.teamInfoModel?.team?.type { - if beInviteMode == .noAuth || type == .normal { - self.didAddUserAndRefreshUI(accids, tid) - } else { - self.didAddUser(accids, tid) - } - } - } - var param = [String: Any]() - param["nav"] = navigationController as Any - var filters = Set() - viewmodel.teamInfoModel?.users.forEach { model in - if let uid = model.nimUser?.userId { - filters.insert(uid) - } - } - if filters.count > 0 { - param["filters"] = filters - } - param["limit"] = 200 - filters.count - Router.shared.use(ContactUserSelectRouter, parameters: param, closure: nil) - } + // MARK: objc 方法 - @objc func removeTeamForMyself() { - weak var weakSelf = self - if viewmodel.isOwner(), let type = viewmodel.teamInfoModel?.team?.type, type == .advanced { - showAlert(message: "是否解散群聊?") { - weakSelf?.dismissTeam() - } - } else { - if let type = viewmodel.teamInfoModel?.team?.type { - if type == .advanced { - showAlert(message: "是否退出群聊?") { - weakSelf?.leveaTeam() - } - } else if type == .normal { - showAlert(message: "是否退出讨论组?") { - weakSelf?.leveaTeam() - } - } + @objc func addUser() { + weak var weakSelf = self + Router.shared.register(ContactSelectedUsersRouter) { param in + print("addUser weak self ", weakSelf as Any) + if let accids = param["accids"] as? [String], + let tid = self.viewmodel.teamInfoModel?.team?.teamId, + let beInviteMode = self.viewmodel.teamInfoModel?.team?.beInviteMode, + let type = self.viewmodel.teamInfoModel?.team?.type { + if beInviteMode == .noAuth || type == .normal { + self.didAddUserAndRefreshUI(accids, tid) + } else { + self.didAddUser(accids, tid) } } } - - @objc func toInfoView() { - let info = TeamInfoViewController() - info.team = viewmodel.teamInfoModel?.team - navigationController?.pushViewController(info, animated: true) + var param = [String: Any]() + param["nav"] = navigationController as Any + var filters = Set() + viewmodel.teamInfoModel?.users.forEach { model in + if let uid = model.nimUser?.userId { + filters.insert(uid) + } + } + if filters.count > 0 { + param["filters"] = filters } - @objc func toMemberList() { - let memberController = TeamMembersController() - memberController.datas = viewmodel.teamInfoModel?.users - if let type = viewmodel.teamInfoModel?.team?.type, type == .advanced { - memberController.isSenior = true - } - memberController.ownerId = viewmodel.teamInfoModel?.team?.owner - navigationController?.pushViewController(memberController, animated: true) - } - - - //MARK: UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout - public func collectionView(_ collectionView: UICollectionView, - numberOfItemsInSection section: Int) -> Int { - print("numberOfItemsInSection ", viewmodel.teamInfoModel?.users.count as Any) - return viewmodel.teamInfoModel?.users.count ?? 0 - } - - public func collectionView(_ collectionView: UICollectionView, - cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - if let cell = collectionView.dequeueReusableCell( - withReuseIdentifier: "\(TeamUserCell.self)", - for: indexPath - ) as? TeamUserCell { - if let user = viewmodel.teamInfoModel?.users[indexPath.row] { - cell.user = user - } - return cell + param["limit"] = 200 - filters.count + Router.shared.use(ContactUserSelectRouter, parameters: param, closure: nil) + } + + @objc func removeTeamForMyself() { + weak var weakSelf = self + if viewmodel.isOwner(), let type = viewmodel.teamInfoModel?.team?.type, type == .advanced { + showAlert(message: localizable("dissolute_team_chat")) { + weakSelf?.dismissTeam() } - return UICollectionViewCell() - } - - public func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - sizeForItemAt indexPath: IndexPath) -> CGSize { - CGSize(width: 47.0, height: 32) - } - - public func collectionView(_ collectionView: UICollectionView, - didSelectItemAt indexPath: IndexPath) { - if let member = viewmodel.teamInfoModel?.users[indexPath.row], - let nimUser = member.nimUser { - let user = User(user: nimUser) - if IMKitLoginManager.instance.isMySelf(user.userId) { - Router.shared.use( - MeSettingRouter, - parameters: ["nav": navigationController as Any], - closure: nil - ) - } else { - Router.shared.use( - ContactUserInfoPageRouter, - parameters: ["nav": navigationController as Any, "user": user], - closure: nil - ) + } else { + if let type = viewmodel.teamInfoModel?.team?.type { + if type == .advanced { + showAlert(message: localizable("quit_team_chat")) { + weakSelf?.leveaTeam() + } + } else if type == .normal { + showAlert(message: localizable("quit_discuss_chat")) { + weakSelf?.leveaTeam() + } } } } - - //MARK: UITableViewDataSource, UITableViewDelegate - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if viewmodel.sectionData.count > section { - let model = viewmodel.sectionData[section] - return model.cellModels.count - } - return 0 - } + } - public func numberOfSections(in tableView: UITableView) -> Int { - viewmodel.sectionData.count + @objc func toInfoView() { + let info = TeamInfoViewController() + info.team = viewmodel.teamInfoModel?.team + navigationController?.pushViewController(info, animated: true) + } + + @objc func toMemberList() { + let memberController = TeamMembersController() + memberController.datas = viewmodel.teamInfoModel?.users + if let type = viewmodel.teamInfoModel?.team?.type, type == .advanced { + memberController.isSenior = true } + memberController.ownerId = viewmodel.teamInfoModel?.team?.owner + navigationController?.pushViewController(memberController, animated: true) + } + + // MARK: UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout - public func tableView(_ tableView: UITableView, - cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let model = viewmodel.sectionData[indexPath.section].cellModels[indexPath.row] - if let cell = tableView.dequeueReusableCell( - withIdentifier: "\(model.type)", - for: indexPath - ) as? BaseTeamSettingCell { - cell.configure(model) - return cell + public func collectionView(_ collectionView: UICollectionView, + numberOfItemsInSection section: Int) -> Int { + print("numberOfItemsInSection ", viewmodel.teamInfoModel?.users.count as Any) + return viewmodel.teamInfoModel?.users.count ?? 0 + } + + public func collectionView(_ collectionView: UICollectionView, + cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + if let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: "\(TeamUserCell.self)", + for: indexPath + ) as? TeamUserCell { + if let user = viewmodel.teamInfoModel?.users[indexPath.row] { + cell.user = user } - return UITableViewCell() + return cell } + return UICollectionViewCell() + } + + public func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath) -> CGSize { + CGSize(width: 47.0, height: 32) + } - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let model = viewmodel.sectionData[indexPath.section].cellModels[indexPath.row] - if let block = model.cellClick { - block() + public func collectionView(_ collectionView: UICollectionView, + didSelectItemAt indexPath: IndexPath) { + if let member = viewmodel.teamInfoModel?.users[indexPath.row], + let nimUser = member.nimUser { + let user = User(user: nimUser) + if IMKitEngine.instance.isMySelf(user.userId) { + Router.shared.use( + MeSettingRouter, + parameters: ["nav": navigationController as Any], + closure: nil + ) + } else { + Router.shared.use( + ContactUserInfoPageRouter, + parameters: ["nav": navigationController as Any, "user": user], + closure: nil + ) } } + } - public func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - let model = viewmodel.sectionData[indexPath.section].cellModels[indexPath.row] - return model.rowHeight - } + // MARK: UITableViewDataSource, UITableViewDelegate - public func tableView(_ tableView: UITableView, - heightForHeaderInSection section: Int) -> CGFloat { - if viewmodel.sectionData.count > section { - let model = viewmodel.sectionData[section] - if model.cellModels.count > 0 { - return 12.0 - } - } - return 0 + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if viewmodel.sectionData.count > section { + let model = viewmodel.sectionData[section] + return model.cellModels.count } + return 0 + } - public func tableView(_ tableView: UITableView, - viewForHeaderInSection section: Int) -> UIView? { - let header = UIView() - header.backgroundColor = NEConstant.hexRGB(0xF1F1F6) - return header + public func numberOfSections(in tableView: UITableView) -> Int { + viewmodel.sectionData.count + } + + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let model = viewmodel.sectionData[indexPath.section].cellModels[indexPath.row] + if let cell = tableView.dequeueReusableCell( + withIdentifier: "\(model.type)", + for: indexPath + ) as? BaseTeamSettingCell { + cell.configure(model) + return cell + } + return UITableViewCell() + } + + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let model = viewmodel.sectionData[indexPath.section].cellModels[indexPath.row] + if let block = model.cellClick { + block() } + } - public func tableView(_ tableView: UITableView, - heightForFooterInSection section: Int) -> CGFloat { - if section == viewmodel.sectionData.count - 1 { + public func tableView(_ tableView: UITableView, + heightForRowAt indexPath: IndexPath) -> CGFloat { + let model = viewmodel.sectionData[indexPath.section].cellModels[indexPath.row] + return model.rowHeight + } + + public func tableView(_ tableView: UITableView, + heightForHeaderInSection section: Int) -> CGFloat { + if viewmodel.sectionData.count > section { + let model = viewmodel.sectionData[section] + if model.cellModels.count > 0 { return 12.0 } - return 0 } - + return 0 + } + + public func tableView(_ tableView: UITableView, + viewForHeaderInSection section: Int) -> UIView? { + let header = UIView() + header.backgroundColor = NEConstant.hexRGB(0xF1F1F6) + return header + } + + public func tableView(_ tableView: UITableView, + heightForFooterInSection section: Int) -> CGFloat { + if section == viewmodel.sectionData.count - 1 { + return 12.0 + } + return 0 + } } extension TeamSettingViewController { @@ -541,13 +545,11 @@ extension TeamSettingViewController { if let err = error { weakSelf?.showToast(err.localizedDescription) } else { - weakSelf?.showToast("邀请已发送") + weakSelf?.showToast(localizable("invite_has_send")) } } } - - func dismissTeam() { if let tid = teamId { weak var weakSelf = self @@ -583,7 +585,6 @@ extension TeamSettingViewController { } } } - } extension TeamSettingViewController: TeamSettingViewModelDelegate { @@ -604,13 +605,13 @@ extension TeamSettingViewController: TeamSettingViewModelDelegate { preferredStyle: .actionSheet ) - let cancelActionButton = UIAlertAction(title: "取消", style: .cancel) { _ in + let cancelActionButton = UIAlertAction(title: localizable("cancel"), style: .cancel) { _ in print("Cancel") } cancelActionButton.setValue(UIColor.ne_darkText, forKey: "_titleTextColor") actionSheetController.addAction(cancelActionButton) - let manager = UIAlertAction(title: "群主", style: .default) { _ in + let manager = UIAlertAction(title: localizable("team_owner"), style: .default) { _ in weakSelf?.view.makeToastActivity(.center) weakSelf?.viewmodel.repo.updateInviteMode(.manager, weakSelf?.teamId ?? "") { error in weakSelf?.view.hideToastActivity() @@ -618,7 +619,7 @@ extension TeamSettingViewController: TeamSettingViewModelDelegate { weakSelf?.showToast(err.localizedDescription) } else { weakSelf?.viewmodel.teamInfoModel?.team?.inviteMode = .manager - model.subTitle = "群主" + model.subTitle = localizable("team_owner") weakSelf?.contentTable.reloadData() } } @@ -626,7 +627,7 @@ extension TeamSettingViewController: TeamSettingViewModelDelegate { manager.setValue(UIColor.ne_darkText, forKey: "_titleTextColor") actionSheetController.addAction(manager) - let deleteActionButton = UIAlertAction(title: "所有人", style: .default) { _ in + let deleteActionButton = UIAlertAction(title: localizable("team_all"), style: .default) { _ in weakSelf?.view.makeToastActivity(.center) weakSelf?.viewmodel.repo.updateInviteMode(.all, weakSelf?.teamId ?? "") { error in weakSelf?.view.hideToastActivity() @@ -634,7 +635,7 @@ extension TeamSettingViewController: TeamSettingViewModelDelegate { weakSelf?.showToast(err.localizedDescription) } else { weakSelf?.viewmodel.teamInfoModel?.team?.inviteMode = .all - model.subTitle = "所有人" + model.subTitle = localizable("team_all") weakSelf?.contentTable.reloadData() } } @@ -648,17 +649,17 @@ extension TeamSettingViewController: TeamSettingViewModelDelegate { func didUpdateTeamInfoClick(_ model: SettingCellModel) { let actionSheetController = UIAlertController( - title: "提示", + title: localizable("remind"), message: nil, preferredStyle: .actionSheet ) weak var weakSelf = self - let cancelActionButton = UIAlertAction(title: "取消", style: .cancel) { _ in + let cancelActionButton = UIAlertAction(title: localizable("cancel"), style: .cancel) { _ in print("Cancel") } actionSheetController.addAction(cancelActionButton) - let manager = UIAlertAction(title: "群主", style: .default) { _ in + let manager = UIAlertAction(title: localizable("team_owner"), style: .default) { _ in weakSelf?.view.makeToastActivity(.center) weakSelf?.viewmodel.repo .updateTeamInfoPrivilege(.manager, weakSelf?.teamId ?? "") { error in @@ -667,14 +668,14 @@ extension TeamSettingViewController: TeamSettingViewModelDelegate { weakSelf?.showToast(err.localizedDescription) } else { weakSelf?.viewmodel.teamInfoModel?.team?.updateInfoMode = .manager - model.subTitle = "群主" + model.subTitle = localizable("team_owner") weakSelf?.contentTable.reloadData() } } } actionSheetController.addAction(manager) - let all = UIAlertAction(title: "所有人", style: .default) { _ in + let all = UIAlertAction(title: localizable("team_all"), style: .default) { _ in weakSelf?.view.makeToastActivity(.center) weakSelf?.viewmodel.repo .updateTeamInfoPrivilege(.all, weakSelf?.teamId ?? "") { error in @@ -683,7 +684,7 @@ extension TeamSettingViewController: TeamSettingViewModelDelegate { weakSelf?.showToast(err.localizedDescription) } else { weakSelf?.viewmodel.teamInfoModel?.team?.updateInfoMode = .all - model.subTitle = "所有人" + model.subTitle = localizable("team_all") weakSelf?.contentTable.reloadData() } } diff --git a/NETeamUIKit/NEKitTeamUI/Classes/Setting/View/TeamMemberCell.swift b/NETeamUIKit/NEKitTeamUI/Classes/Setting/View/TeamMemberCell.swift index 777bfc6b..09d57489 100644 --- a/NETeamUIKit/NEKitTeamUI/Classes/Setting/View/TeamMemberCell.swift +++ b/NETeamUIKit/NEKitTeamUI/Classes/Setting/View/TeamMemberCell.swift @@ -28,7 +28,7 @@ class TeamMemberCell: UITableViewCell { label.layer.cornerRadius = 4.0 label.layer.borderColor = NEConstant.hexRGB(0xB9D3FF).cgColor label.layer.borderWidth = 1.0 - label.text = "群主" + label.text = localizable("team_owner") label.textAlignment = .center return label }() diff --git a/NETeamUIKit/NEKitTeamUI/Classes/Setting/ViewModel/TeamInfoViewModel.swift b/NETeamUIKit/NEKitTeamUI/Classes/Setting/ViewModel/TeamInfoViewModel.swift index 31245dd2..d5c02e2a 100644 --- a/NETeamUIKit/NEKitTeamUI/Classes/Setting/ViewModel/TeamInfoViewModel.swift +++ b/NETeamUIKit/NEKitTeamUI/Classes/Setting/ViewModel/TeamInfoViewModel.swift @@ -26,9 +26,9 @@ class TeamInfoViewModel { intrCell.cornerType = .bottomLeft.union(.bottomRight) if let type = team?.type, type == .normal { - headerCell.cellName = "讨论组头像" - nameCell.cellName = "讨论组名称" - intrCell.cellName = "讨论组介绍" + headerCell.cellName = localizable("discuss_avatar") + nameCell.cellName = localizable("discuss_name") + intrCell.cellName = localizable("discuss_intro") cellDatas.append(contentsOf: [headerCell, nameCell]) nameCell.cornerType = .bottomLeft.union(.bottomRight) } else { diff --git a/NETeamUIKit/NEKitTeamUI/Classes/Setting/ViewModel/TeamSettingViewModel.swift b/NETeamUIKit/NEKitTeamUI/Classes/Setting/ViewModel/TeamSettingViewModel.swift index 203480e3..7181b477 100644 --- a/NETeamUIKit/NEKitTeamUI/Classes/Setting/ViewModel/TeamSettingViewModel.swift +++ b/NETeamUIKit/NEKitTeamUI/Classes/Setting/ViewModel/TeamSettingViewModel.swift @@ -283,7 +283,7 @@ public class TeamSettingViewModel { if error == nil { weakSelf?.getData() - weakSelf?.getCurrentMember(IMKitLoginManager.instance.imAccid, teamId) + weakSelf?.getCurrentMember(IMKitEngine.instance.imAccid, teamId) } completion(error) } @@ -307,7 +307,7 @@ public class TeamSettingViewModel { func isOwner() -> Bool { if let accid = teamInfoModel?.team?.owner { - if IMKitLoginManager.instance.isMySelf(accid) { + if IMKitEngine.instance.isMySelf(accid) { return true } } diff --git a/NETeamUIKit/NEKitTeamUI/Classes/TeamRouter.swift b/NETeamUIKit/NEKitTeamUI/Classes/TeamRouter.swift index a5245736..4af5aa66 100644 --- a/NETeamUIKit/NEKitTeamUI/Classes/TeamRouter.swift +++ b/NETeamUIKit/NEKitTeamUI/Classes/TeamRouter.swift @@ -8,7 +8,9 @@ import NEKitTeam import NEKitCore import NEKitCoreIM import NIMSDK -public enum TeamRouter { + +@objcMembers +public class TeamRouter:NSObject { public static var iconUrls = ["https://s.netease.im/safe/ABg8YjWQWvcqO6sAAAAAAAAAAAA?_im_url=1", "https://s.netease.im/safe/ABg8YjmQWvcqO6sAAAAAAAABAAA?_im_url=1", "https://s.netease.im/safe/ABg8YjyQWvcqO6sAAAAAAAABAAA?_im_url=1", From 170c829ae07c8c133f0b43c458daaee8d91e857b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=96=84=E6=A0=8B?= Date: Mon, 19 Sep 2022 16:18:40 +0800 Subject: [PATCH 3/6] =?UTF-8?q?mine=20=E6=A8=A1=E5=9D=97=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Mine/BirthdayDatePickerView.swift | 243 ++++++----- app/Mine/InputPersonInfoController.swift | 33 +- app/Mine/IntroduceBrandViewController.swift | 73 ++-- app/Mine/IntroduceViewModel.swift | 32 +- app/Mine/MeViewController.swift | 365 ++++++++-------- app/Mine/MessageRemindViewController.swift | 94 ++--- app/Mine/MessageRemindViewModel.swift | 142 +++---- app/Mine/MineSettingViewController.swift | 105 +++-- app/Mine/MineSettingViewModel.swift | 127 +++--- app/Mine/MineTableViewCell.swift | 157 ++++--- app/Mine/NEAboutWebViewController.swift | 73 ++-- app/Mine/PersonInfoViewController.swift | 312 +++++++------- app/Mine/PersonInfoViewModel.swift | 445 ++++++++++---------- app/Mine/VersionCell.swift | 197 +++++---- 14 files changed, 1197 insertions(+), 1201 deletions(-) diff --git a/app/Mine/BirthdayDatePickerView.swift b/app/Mine/BirthdayDatePickerView.swift index 118108b0..8a6b7319 100644 --- a/app/Mine/BirthdayDatePickerView.swift +++ b/app/Mine/BirthdayDatePickerView.swift @@ -1,134 +1,127 @@ -// Copyright (c) 2022 NetEase, Inc. All rights reserved. -// Use of this source code is governed by a MIT license that can be found in the LICENSE file. +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. import UIKit +import NEKitCommonUI public class BirthdayDatePickerView: UIView { + private var selectTime: String? + public typealias SelectTimeCallBack = (String?) -> Void + public var timeCallBack: SelectTimeCallBack? - private var selectTime:String? - public typealias SelectTimeCallBack = (String?) -> Void - public var timeCallBack:SelectTimeCallBack? - - lazy var sureBtn:UIButton = { - let button = UIButton.init(type: .custom) - button.translatesAutoresizingMaskIntoConstraints = false - button.setTitle("确认", for: .normal) - button.setTitleColor(UIColor.ne_blueText, for: .normal) - button.titleLabel?.font = UIFont.systemFont(ofSize: 13) - button.addTarget(self, action: #selector(sureBtnClick), for: .touchUpInside) - return button - }() - - lazy var picker:UIDatePicker = { - let datePicker = UIDatePicker(frame: CGRect.zero) - datePicker.translatesAutoresizingMaskIntoConstraints = false - //将日期选择器区域设置为中文,则选择器日期显示为中文 - datePicker.locale = Locale(identifier: "zh_CN") - datePicker.datePickerMode = .date - //注意:action里面的方法名后面需要加个冒号“:” - datePicker.addTarget(self, action: #selector(dateChanged), - for: .valueChanged) - - return datePicker - }() - - lazy var cancelBtn:UIButton = { - let button = UIButton() - button.translatesAutoresizingMaskIntoConstraints = false - button.setTitle("取消", for: .normal) - button.setTitleColor(UIColor.ne_blueText, for: .normal) - button.titleLabel?.font = UIFont.systemFont(ofSize: 13) - button.addTarget(self, action: #selector(cancelBtnClick), for: .touchUpInside) - - return button - }() - - override init(frame: CGRect) { - super.init(frame: frame) - setupSubviews() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func setupSubviews(){ - //创建日期选择器 - self.addSubview(cancelBtn) - self.addSubview(sureBtn) - self.addSubview(bottomLine) - self.addSubview(picker) - - NSLayoutConstraint.activate([ - cancelBtn.leftAnchor.constraint(equalTo: self.leftAnchor,constant: 15), - cancelBtn.topAnchor.constraint(equalTo: self.topAnchor,constant: 8), - cancelBtn.widthAnchor.constraint(equalToConstant: 45) - ]) - - - NSLayoutConstraint.activate([ - sureBtn.rightAnchor.constraint(equalTo: self.rightAnchor,constant: -15), - sureBtn.topAnchor.constraint(equalTo: self.topAnchor,constant: 8), - sureBtn.widthAnchor.constraint(equalToConstant: 45) - ]) - - - NSLayoutConstraint.activate([ - bottomLine.leftAnchor.constraint(equalTo: self.leftAnchor), - bottomLine.rightAnchor.constraint(equalTo: self.rightAnchor), - bottomLine.topAnchor.constraint(equalTo: cancelBtn.bottomAnchor), - bottomLine.heightAnchor.constraint(equalToConstant: 0.5) - ]) - - - NSLayoutConstraint.activate([ - picker.leftAnchor.constraint(equalTo: self.leftAnchor), - picker.rightAnchor.constraint(equalTo: self.rightAnchor), - picker.bottomAnchor.constraint(equalTo: self.bottomAnchor), - picker.topAnchor.constraint(equalTo: bottomLine.bottomAnchor) - ]) + lazy var sureBtn: UIButton = { + let button = UIButton(type: .custom) + button.translatesAutoresizingMaskIntoConstraints = false + button.setTitle(NSLocalizedString("confirm", comment: ""), for: .normal) + button.setTitleColor(UIColor.ne_blueText, for: .normal) + button.titleLabel?.font = UIFont.systemFont(ofSize: 13) + button.addTarget(self, action: #selector(sureBtnClick), for: .touchUpInside) + return button + }() + lazy var picker: UIDatePicker = { + let datePicker = UIDatePicker(frame: CGRect.zero) + datePicker.translatesAutoresizingMaskIntoConstraints = false + // 将日期选择器区域设置为中文,则选择器日期显示为中文 + datePicker.locale = Locale(identifier: "zh_CN") + datePicker.datePickerMode = .date + // 注意:action里面的方法名后面需要加个冒号“:” + datePicker.addTarget(self, action: #selector(dateChanged), + for: .valueChanged) + + return datePicker + }() + + lazy var cancelBtn: UIButton = { + let button = UIButton() + button.translatesAutoresizingMaskIntoConstraints = false + button.setTitle(NSLocalizedString("cancel", comment: ""), for: .normal) + button.setTitleColor(UIColor.ne_blueText, for: .normal) + button.titleLabel?.font = UIFont.systemFont(ofSize: 13) + button.addTarget(self, action: #selector(cancelBtnClick), for: .touchUpInside) + + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setupSubviews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupSubviews() { + // 创建日期选择器 + addSubview(cancelBtn) + addSubview(sureBtn) + addSubview(bottomLine) + addSubview(picker) + + NSLayoutConstraint.activate([ + cancelBtn.leftAnchor.constraint(equalTo: leftAnchor, constant: 15), + cancelBtn.topAnchor.constraint(equalTo: topAnchor, constant: 8), + cancelBtn.widthAnchor.constraint(equalToConstant: 45), + ]) + + NSLayoutConstraint.activate([ + sureBtn.rightAnchor.constraint(equalTo: rightAnchor, constant: -15), + sureBtn.topAnchor.constraint(equalTo: topAnchor, constant: 8), + sureBtn.widthAnchor.constraint(equalToConstant: 45), + ]) + + NSLayoutConstraint.activate([ + bottomLine.leftAnchor.constraint(equalTo: leftAnchor), + bottomLine.rightAnchor.constraint(equalTo: rightAnchor), + bottomLine.topAnchor.constraint(equalTo: cancelBtn.bottomAnchor), + bottomLine.heightAnchor.constraint(equalToConstant: 0.5), + ]) + + NSLayoutConstraint.activate([ + picker.leftAnchor.constraint(equalTo: leftAnchor), + picker.rightAnchor.constraint(equalTo: rightAnchor), + picker.bottomAnchor.constraint(equalTo: bottomAnchor), + picker.topAnchor.constraint(equalTo: bottomLine.bottomAnchor), + ]) + } + + @objc func dateChanged(datePicker: UIDatePicker) { + // 更新提醒时间文本框 + let formatter = DateFormatter() + // 日期样式 + formatter.dateFormat = "yyyy-MM-dd" + let time = formatter.string(from: datePicker.date) + selectTime = time + } + + @objc func sureBtnClick(sender: UIButton) { + removeFromSuperview() + weak var weakSelf = self + if let time = selectTime { + if let block = timeCallBack { + block(time) + } + } else { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + selectTime = formatter.string(from: picker.date) + if let block = timeCallBack { + block(weakSelf?.selectTime) + } } - - - @objc func dateChanged(datePicker : UIDatePicker){ - //更新提醒时间文本框 - let formatter = DateFormatter() - //日期样式 - formatter.dateFormat = "yyyy-MM-dd" - let time = formatter.string(from: datePicker.date) - selectTime = time - } - - - - @objc func sureBtnClick(sender:UIButton) { - self.removeFromSuperview() - weak var weakSelf = self - if let time = selectTime { - if let block = timeCallBack { - block(time) - } - }else { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" - selectTime = formatter.string(from: picker.date) - if let block = timeCallBack { - block(weakSelf?.selectTime) - } - } - } - - @objc func cancelBtnClick(sender:UIButton) { - self.removeFromSuperview() - } - - private lazy var bottomLine:UIView = { - let view = UIView() - view.translatesAutoresizingMaskIntoConstraints = false - view.backgroundColor = UIColor.init(hexString: "0xDBE0E8") - return view - }() - + } + + @objc func cancelBtnClick(sender: UIButton) { + removeFromSuperview() + } + + private lazy var bottomLine: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + view.backgroundColor = UIColor(hexString: "0xDBE0E8") + return view + }() } diff --git a/app/Mine/InputPersonInfoController.swift b/app/Mine/InputPersonInfoController.swift index 73b51556..1cfb1377 100644 --- a/app/Mine/InputPersonInfoController.swift +++ b/app/Mine/InputPersonInfoController.swift @@ -14,7 +14,7 @@ public enum EditType: Int { case specialSign } -class InputPersonInfoController: NEBaseViewController,UITextFieldDelegate { +class InputPersonInfoController: NEBaseViewController, UITextFieldDelegate { typealias ResultCallBack = (String) -> Void public var contentText: String? { didSet { @@ -61,7 +61,7 @@ class InputPersonInfoController: NEBaseViewController,UITextFieldDelegate { } func initialConfig() { - addRightAction("保存", #selector(saveName), self) + addRightAction(NSLocalizedString("save", comment: ""), #selector(saveName), self) view.backgroundColor = UIColor(hexString: "0xF1F1F6") } @@ -76,16 +76,16 @@ class InputPersonInfoController: NEBaseViewController,UITextFieldDelegate { func configTitle(editType: EditType) { switch editType { case .nickName: - title = "昵称" + title = NSLocalizedString("nickname", comment: "") limitNumberCount = 30 case .cellphone: - title = "手机" + title = NSLocalizedString("phone", comment: "") limitNumberCount = 11 case .email: - title = "邮箱" + title = NSLocalizedString("email", comment: "") limitNumberCount = 30 case .specialSign: - title = "个性签名" + title = NSLocalizedString("individuality_sign", comment: "") limitNumberCount = 50 } } @@ -111,15 +111,16 @@ class InputPersonInfoController: NEBaseViewController,UITextFieldDelegate { backView.translatesAutoresizingMaskIntoConstraints = false return backView }() - //MARK: UITextFieldDelegate - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, - replacementString string: String) -> Bool { - if let text = (textField.text as NSString?)?.replacingCharacters(in: range, with: string), - text.count > limitNumberCount { - showToast("最多只能输入\(limitNumberCount)个字符哦") - return false - } - return true + + // MARK: UITextFieldDelegate + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, + replacementString string: String) -> Bool { + if let text = (textField.text as NSString?)?.replacingCharacters(in: range, with: string), + text.count > limitNumberCount { + showToast("最多只能输入\(limitNumberCount)个字符哦") + return false } + return true + } } - diff --git a/app/Mine/IntroduceBrandViewController.swift b/app/Mine/IntroduceBrandViewController.swift index 7706c586..ae43f49b 100644 --- a/app/Mine/IntroduceBrandViewController.swift +++ b/app/Mine/IntroduceBrandViewController.swift @@ -8,7 +8,8 @@ import NEKitCore import NEKitTeamUI import NEKitChatUI -class IntroduceBrandViewController: NEBaseViewController,UITableViewDelegate, UITableViewDataSource { +class IntroduceBrandViewController: NEBaseViewController, UITableViewDelegate, + UITableViewDataSource { private var viewModel = IntroduceViewModel() override func viewDidLoad() { @@ -24,6 +25,8 @@ class IntroduceBrandViewController: NEBaseViewController,UITableViewDelegate, UI func setupSubviews() { view.addSubview(headImage) + view.addSubview(headLable) + view.addSubview(tableView) NSLayoutConstraint.activate([ @@ -32,9 +35,14 @@ class IntroduceBrandViewController: NEBaseViewController,UITableViewDelegate, UI equalTo: view.topAnchor, constant: kNavigationHeight + KStatusBarHeight + 20 ), - headImage.widthAnchor.constraint(equalToConstant: 94), - headImage.heightAnchor.constraint(equalToConstant: 66), + headImage.widthAnchor.constraint(equalToConstant: 72), + headImage.heightAnchor.constraint(equalToConstant: 53), + + ]) + NSLayoutConstraint.activate([ + headLable.centerXAnchor.constraint(equalTo: headImage.centerXAnchor), + headLable.topAnchor.constraint(equalTo: headImage.bottomAnchor, constant: 10), ]) NSLayoutConstraint.activate([ @@ -53,6 +61,15 @@ class IntroduceBrandViewController: NEBaseViewController,UITableViewDelegate, UI return image }() + private lazy var headLable: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.text = NSLocalizedString("brand_des", comment: "") + label.font = UIFont.systemFont(ofSize: 20.0) + label.textColor = UIColor(hexString: "333333") + return label + }() + lazy var tableView: UITableView = { let table = UITableView() table.translatesAutoresizingMaskIntoConstraints = false @@ -66,34 +83,34 @@ class IntroduceBrandViewController: NEBaseViewController,UITableViewDelegate, UI } return table }() - - //MARK: UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - viewModel.sectionData.count - } - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let model = viewModel.sectionData[indexPath.row] - if let cell = tableView.dequeueReusableCell( - withIdentifier: "VersionCell", - for: indexPath - ) as? VersionCell { - cell.configData(model: model) - if indexPath.row == 0 { - cell.cellType = .version - } else { - cell.cellType = .productIntroduce - } - return cell + // MARK: UITableViewDelegate, UITableViewDataSource + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + viewModel.sectionData.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let model = viewModel.sectionData[indexPath.row] + if let cell = tableView.dequeueReusableCell( + withIdentifier: "VersionCell", + for: indexPath + ) as? VersionCell { + cell.configData(model: model) + if indexPath.row == 0 { + cell.cellType = .version + } else { + cell.cellType = .productIntroduce } - return UITableViewCell() + return cell } + return UITableViewCell() + } - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if indexPath.row == 1 { - let ctrl = NEAboutWebViewController(url: "https://netease.im/m/") - navigationController?.pushViewController(ctrl, animated: true) - } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.row == 1 { + let ctrl = NEAboutWebViewController(url: "https://netease.im/m/") + navigationController?.pushViewController(ctrl, animated: true) } + } } - diff --git a/app/Mine/IntroduceViewModel.swift b/app/Mine/IntroduceViewModel.swift index 127a5faf..00fa2a3c 100644 --- a/app/Mine/IntroduceViewModel.swift +++ b/app/Mine/IntroduceViewModel.swift @@ -1,25 +1,23 @@ -// Copyright (c) 2022 NetEase, Inc. All rights reserved. -// Use of this source code is governed by a MIT license that can be found in the LICENSE file. +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. import Foundation import NEKitTeamUI public class IntroduceViewModel { - var sectionData = [SettingCellModel]() + var sectionData = [SettingCellModel]() - func getData(){ - let versionItem = SettingCellModel() - versionItem.cellName = "版本号" - let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String - if let version = appVersion { - versionItem.subTitle = "V\(version)" - } - - let introduceItem = SettingCellModel() - introduceItem.cellName = "产品介绍" - sectionData.append(contentsOf: [versionItem,introduceItem]) + func getData() { + let versionItem = SettingCellModel() + versionItem.cellName = NSLocalizedString("version", comment: "") + let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + if let version = appVersion { + versionItem.subTitle = "V\(version)" } - - - + + let introduceItem = SettingCellModel() + introduceItem.cellName = NSLocalizedString("product_intro", comment: "") + sectionData.append(contentsOf: [versionItem, introduceItem]) + } } diff --git a/app/Mine/MeViewController.swift b/app/Mine/MeViewController.swift index bdc4bdf1..ba073b4f 100644 --- a/app/Mine/MeViewController.swift +++ b/app/Mine/MeViewController.swift @@ -1,6 +1,7 @@ -// Copyright (c) 2022 NetEase, Inc. All rights reserved. -// Use of this source code is governed by a MIT license that can be found in the LICENSE file. +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. import UIKit import YXLogin @@ -8,181 +9,184 @@ import NEKitCore import NIMSDK import NEKitCoreIM import NEKitQChatUI -import YXLogin class MeViewController: UIViewController { - // private let mineData = [["收藏":"mine_collection"],["关于云信":"about_yunxin"],["设置":"mine_setting"]] - private let mineData = [["关于云信":"about_yunxin"],["设置":"mine_setting"]] - private let userProvider = UserInfoProvider.shared - - lazy var header: NEUserHeaderView = { - let view = NEUserHeaderView(frame: .zero) - view.titleLabel.font = UIFont.systemFont(ofSize: 22.0) - view.translatesAutoresizingMaskIntoConstraints = false - return view - }() - - lazy var nameLabel: UILabel = { - let name = UILabel() - name.textColor = .ne_darkText - name.font = UIFont.systemFont(ofSize: 22.0) - name.translatesAutoresizingMaskIntoConstraints = false - return name - }() - - lazy var idLabel: UILabel = { - let label = UILabel() - label.textColor = .ne_darkText - label.font = UIFont.systemFont(ofSize: 16.0) - label.translatesAutoresizingMaskIntoConstraints = false - return label - }() - - - override func viewDidLoad() { - super.viewDidLoad() - setupSubviews() - } - - override func viewWillAppear(_ animated: Bool) { - navigationController?.setNavigationBarHidden(true, animated: false) - updateUserInfo() - super.viewWillAppear(animated) - } - - func setupSubviews(){ - - view.addSubview(header) - if #available(iOS 11.0, *) { - NSLayoutConstraint.activate([ - header.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20), - header.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 20), - header.widthAnchor.constraint(equalToConstant: 60), - header.heightAnchor.constraint(equalToConstant: 60) - ]) - } else { - // Fallback on earlier versions - NSLayoutConstraint.activate([ - header.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20), - header.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 20), - header.widthAnchor.constraint(equalToConstant: 60), - header.heightAnchor.constraint(equalToConstant: 60) - ]) - } - header.clipsToBounds = true - header.layer.cornerRadius = 30 - - view.addSubview(nameLabel) - NSLayoutConstraint.activate([ - nameLabel.leftAnchor.constraint(equalTo: header.rightAnchor, constant: 15), - nameLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20), - nameLabel.topAnchor.constraint(equalTo: header.topAnchor) - ]) - - view.addSubview(idLabel) - NSLayoutConstraint.activate([ - idLabel.leftAnchor.constraint(equalTo: nameLabel.leftAnchor), - idLabel.rightAnchor.constraint(equalTo: nameLabel.rightAnchor), - idLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 8) - ]) - - //更新个人信息 - updateUserInfo() - - - let divider = UIView() - view.addSubview(divider) - divider.translatesAutoresizingMaskIntoConstraints = false - divider.backgroundColor = UIColor(hexString: "EFF1F4") - NSLayoutConstraint.activate([ - divider.leftAnchor.constraint(equalTo: view.leftAnchor), - divider.heightAnchor.constraint(equalToConstant: 6), - divider.rightAnchor.constraint(equalTo: view.rightAnchor), - divider.topAnchor.constraint(equalTo: header.bottomAnchor, constant: 32) - ]) - - view.addSubview(tableView) - view.addSubview(arrow) - view.addSubview(personInfoBtn) - - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: divider.bottomAnchor), - tableView.leftAnchor.constraint(equalTo: view.leftAnchor), - tableView.rightAnchor.constraint(equalTo: view.rightAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) - - NSLayoutConstraint.activate([ - arrow.centerYAnchor.constraint(equalTo: header.centerYAnchor), - arrow.rightAnchor.constraint(equalTo: view.rightAnchor,constant: -20), - ]) - - NSLayoutConstraint.activate([ - personInfoBtn.topAnchor.constraint(equalTo: header.topAnchor), - personInfoBtn.leftAnchor.constraint(equalTo: view.leftAnchor), - personInfoBtn.rightAnchor.constraint(equalTo: view.rightAnchor), - personInfoBtn.bottomAnchor.constraint(equalTo: divider.topAnchor) - ]) - } - - func updateUserInfo(){ - let user = userProvider.getUserInfo(userId: IMKitLoginManager.instance.imAccid) - idLabel.text = "账号:\(user?.userId ?? "")" - nameLabel.text = user?.userInfo?.nickName - header.configHeadData(headUrl: user?.userInfo?.avatarUrl, name: user?.showName() ?? "") - } - - //MAKR: lazy method - private lazy var tableView:UITableView = { - let tableView = UITableView.init(frame: .zero, style: .plain) - tableView.translatesAutoresizingMaskIntoConstraints = false - tableView.separatorStyle = .none - tableView.delegate = self - tableView.dataSource = self - tableView.register(MineTableViewCell.self, forCellReuseIdentifier: "\(NSStringFromClass(MineTableViewCell.self))") - tableView.rowHeight = 52 - tableView.backgroundColor = .white - return tableView - }() - - private lazy var arrow: UIImageView = { - let imageView = UIImageView(image:UIImage.init(named: "arrow_right")) - imageView.translatesAutoresizingMaskIntoConstraints = false - return imageView - }() - - private lazy var personInfoBtn:UIButton = { - let btn = UIButton() - btn.translatesAutoresizingMaskIntoConstraints = false - btn.addTarget(self, action: #selector(personInfoBtnClick), for: .touchUpInside) - return btn - - }() - - - @objc func personInfoBtnClick(sender:UIButton){ - let personInfo = PersonInfoViewController() - navigationController?.pushViewController(personInfo, animated: true) + private let mineData = [ + [NSLocalizedString("about_yunxin", comment: ""): "about_yunxin"], + [NSLocalizedString("setting", comment: ""): "mine_setting"], + ] + private let userProvider = UserInfoProvider.shared + + lazy var header: NEUserHeaderView = { + let view = NEUserHeaderView(frame: .zero) + view.titleLabel.font = UIFont.systemFont(ofSize: 22.0) + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + lazy var nameLabel: UILabel = { + let name = UILabel() + name.textColor = .ne_darkText + name.font = UIFont.systemFont(ofSize: 22.0) + name.translatesAutoresizingMaskIntoConstraints = false + return name + }() + + lazy var idLabel: UILabel = { + let label = UILabel() + label.textColor = .ne_darkText + label.font = UIFont.systemFont(ofSize: 16.0) + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + override func viewDidLoad() { + super.viewDidLoad() + setupSubviews() + } + + override func viewWillAppear(_ animated: Bool) { + navigationController?.setNavigationBarHidden(true, animated: false) + updateUserInfo() + super.viewWillAppear(animated) + } + + func setupSubviews() { + view.addSubview(header) + if #available(iOS 11.0, *) { + NSLayoutConstraint.activate([ + header.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20), + header.topAnchor.constraint( + equalTo: self.view.safeAreaLayoutGuide.topAnchor, + constant: 20 + ), + header.widthAnchor.constraint(equalToConstant: 60), + header.heightAnchor.constraint(equalToConstant: 60), + ]) + } else { + // Fallback on earlier versions + NSLayoutConstraint.activate([ + header.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20), + header.topAnchor.constraint(equalTo: view.topAnchor, constant: 20), + header.widthAnchor.constraint(equalToConstant: 60), + header.heightAnchor.constraint(equalToConstant: 60), + ]) } + header.clipsToBounds = true + header.layer.cornerRadius = 30 + + view.addSubview(nameLabel) + NSLayoutConstraint.activate([ + nameLabel.leftAnchor.constraint(equalTo: header.rightAnchor, constant: 15), + nameLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20), + nameLabel.topAnchor.constraint(equalTo: header.topAnchor), + ]) + + view.addSubview(idLabel) + NSLayoutConstraint.activate([ + idLabel.leftAnchor.constraint(equalTo: nameLabel.leftAnchor), + idLabel.rightAnchor.constraint(equalTo: nameLabel.rightAnchor), + idLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 8), + ]) + + // 更新个人信息 + updateUserInfo() + let divider = UIView() + view.addSubview(divider) + divider.translatesAutoresizingMaskIntoConstraints = false + divider.backgroundColor = UIColor(hexString: "EFF1F4") + NSLayoutConstraint.activate([ + divider.leftAnchor.constraint(equalTo: view.leftAnchor), + divider.heightAnchor.constraint(equalToConstant: 6), + divider.rightAnchor.constraint(equalTo: view.rightAnchor), + divider.topAnchor.constraint(equalTo: header.bottomAnchor, constant: 32), + ]) + + view.addSubview(tableView) + view.addSubview(arrow) + view.addSubview(personInfoBtn) + + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint(equalTo: divider.bottomAnchor), + tableView.leftAnchor.constraint(equalTo: view.leftAnchor), + tableView.rightAnchor.constraint(equalTo: view.rightAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + + NSLayoutConstraint.activate([ + arrow.centerYAnchor.constraint(equalTo: header.centerYAnchor), + arrow.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20), + ]) + + NSLayoutConstraint.activate([ + personInfoBtn.topAnchor.constraint(equalTo: header.topAnchor), + personInfoBtn.leftAnchor.constraint(equalTo: view.leftAnchor), + personInfoBtn.rightAnchor.constraint(equalTo: view.rightAnchor), + personInfoBtn.bottomAnchor.constraint(equalTo: divider.topAnchor), + ]) + } + + func updateUserInfo() { + let user = userProvider.getUserInfo(userId: IMKitEngine.instance.imAccid) + idLabel.text = "账号:\(user?.userId ?? "")" + nameLabel.text = user?.userInfo?.nickName + header.configHeadData(headUrl: user?.userInfo?.avatarUrl, name: user?.showName() ?? "") + } + + // MAKR: lazy method + private lazy var tableView: UITableView = { + let tableView = UITableView(frame: .zero, style: .plain) + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.separatorStyle = .none + tableView.delegate = self + tableView.dataSource = self + tableView.register( + MineTableViewCell.self, + forCellReuseIdentifier: "\(NSStringFromClass(MineTableViewCell.self))" + ) + tableView.rowHeight = 52 + tableView.backgroundColor = .white + return tableView + }() + + private lazy var arrow: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "arrow_right")) + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + private lazy var personInfoBtn: UIButton = { + let btn = UIButton() + btn.translatesAutoresizingMaskIntoConstraints = false + btn.addTarget(self, action: #selector(personInfoBtnClick), for: .touchUpInside) + return btn + + }() + + @objc func personInfoBtnClick(sender: UIButton) { + let personInfo = PersonInfoViewController() + navigationController?.pushViewController(personInfo, animated: true) + } } -extension MeViewController:UITableViewDelegate,UITableViewDataSource { - - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return mineData.count - } - - public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "\(NSStringFromClass(MineTableViewCell.self))", for: indexPath) as! MineTableViewCell - cell.configCell(data: mineData[indexPath.row]) - return cell - - } - - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - +extension MeViewController: UITableViewDelegate, UITableViewDataSource { + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + mineData.count + } + + public func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell( + withIdentifier: "\(NSStringFromClass(MineTableViewCell.self))", + for: indexPath + ) as! MineTableViewCell + cell.configCell(data: mineData[indexPath.row]) + return cell + } + + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // if indexPath.row == 0 { // // }else if indexPath.row == 1{ @@ -192,18 +196,13 @@ extension MeViewController:UITableViewDelegate,UITableViewDataSource { // let ctrl = MineSettingViewController() // navigationController?.pushViewController(ctrl, animated: true) // } - - if indexPath.row == 0 { - let ctrl = IntroduceBrandViewController() - navigationController?.pushViewController(ctrl, animated: true) - }else if indexPath.row == 1{ - let ctrl = MineSettingViewController() - navigationController?.pushViewController(ctrl, animated: true) - }else if indexPath.row == 2{ - - } - - - } + if indexPath.row == 0 { + let ctrl = IntroduceBrandViewController() + navigationController?.pushViewController(ctrl, animated: true) + } else if indexPath.row == 1 { + let ctrl = MineSettingViewController() + navigationController?.pushViewController(ctrl, animated: true) + } else if indexPath.row == 2 {} + } } diff --git a/app/Mine/MessageRemindViewController.swift b/app/Mine/MessageRemindViewController.swift index 8add738c..37068fff 100644 --- a/app/Mine/MessageRemindViewController.swift +++ b/app/Mine/MessageRemindViewController.swift @@ -8,7 +8,8 @@ import NEKitCore import NEKitTeamUI import NEKitChatUI -class MessageRemindViewController: NEBaseViewController,UITableViewDelegate, UITableViewDataSource { +class MessageRemindViewController: NEBaseViewController, UITableViewDelegate, + UITableViewDataSource { public var cellClassDic = [SettingCellType.SettingSwitchCell.rawValue: TeamSettingSwitchCell.self] private var viewModel = MessageRemindViewModel() @@ -21,7 +22,7 @@ class MessageRemindViewController: NEBaseViewController,UITableViewDelegate, UIT } func initialConfig() { - title = "消息提醒" + title = NSLocalizedString("message_remind", comment: "") } func setupSubviews() { @@ -52,59 +53,58 @@ class MessageRemindViewController: NEBaseViewController,UITableViewDelegate, UIT } return table }() - - //MARK: UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if viewModel.sectionData.count > section { - let model = viewModel.sectionData[section] - return model.cellModels.count - } - return 0 - } - func numberOfSections(in tableView: UITableView) -> Int { - viewModel.sectionData.count - } + // MARK: UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] - if let cell = tableView.dequeueReusableCell( - withIdentifier: "\(model.type)", - for: indexPath - ) as? BaseTeamSettingCell { - cell.configure(model) - return cell - } - return UITableViewCell() + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if viewModel.sectionData.count > section { + let model = viewModel.sectionData[section] + return model.cellModels.count } + return 0 + } - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - // let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] - // if let block = model.cellClick { - // block() - // } - } + func numberOfSections(in tableView: UITableView) -> Int { + viewModel.sectionData.count + } - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] - return model.rowHeight + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] + if let cell = tableView.dequeueReusableCell( + withIdentifier: "\(model.type)", + for: indexPath + ) as? BaseTeamSettingCell { + cell.configure(model) + return cell } + return UITableViewCell() + } - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - if viewModel.sectionData.count > section { - let model = viewModel.sectionData[section] - if model.cellModels.count > 0 { - return 12.0 - } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + // let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] + // if let block = model.cellClick { + // block() + // } + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] + return model.rowHeight + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + if viewModel.sectionData.count > section { + let model = viewModel.sectionData[section] + if model.cellModels.count > 0 { + return 12.0 } - return 0 } + return 0 + } - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let header = UIView() - header.backgroundColor = UIColor(hexString: "0xF1F1F6") - return header - } + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let header = UIView() + header.backgroundColor = UIColor(hexString: "0xF1F1F6") + return header + } } - - diff --git a/app/Mine/MessageRemindViewModel.swift b/app/Mine/MessageRemindViewModel.swift index 2c11f48a..4aaed11e 100644 --- a/app/Mine/MessageRemindViewModel.swift +++ b/app/Mine/MessageRemindViewModel.swift @@ -1,89 +1,83 @@ -// Copyright (c) 2022 NetEase, Inc. All rights reserved. -// Use of this source code is governed by a MIT license that can be found in the LICENSE file. +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. import Foundation import NEKitTeamUI public class MessageRemindViewModel { - var sectionData = [SettingSectionModel]() - - let repo = SettingRepo() + var sectionData = [SettingSectionModel]() - func getData(){ - sectionData.append(getFirstSection()) + let repo = SettingRepo() + + func getData() { + sectionData.append(getFirstSection()) // sectionData.append(getSecondSection()) - sectionData.append(getThirdSection()) + sectionData.append(getThirdSection()) + } + private func getFirstSection() -> SettingSectionModel { + let model = SettingSectionModel() + weak var weakSelf = self + let messageNotify = SettingCellModel() + messageNotify.cellName = NSLocalizedString("new_message_remind", comment: "") + messageNotify.type = SettingCellType.SettingSwitchCell.rawValue + messageNotify.cornerType = .topLeft.union(.topRight).union(.bottomLeft).union(.bottomRight) + messageNotify.switchOpen = repo.getPushEnable() + messageNotify.swichChange = { isOpen in + weakSelf?.repo.setPushEnable(isOpen) } + model.cellModels.append(contentsOf: [messageNotify]) + return model + } - - private func getFirstSection() -> SettingSectionModel { - - let model = SettingSectionModel() - weak var weakSelf = self - let messageNotify = SettingCellModel() - messageNotify.cellName = "新消息通知" - messageNotify.type = SettingCellType.SettingSwitchCell.rawValue - messageNotify.cornerType = .topLeft.union(.topRight).union(.bottomLeft).union(.bottomRight) - messageNotify.switchOpen = repo.getPushEnable() - messageNotify.swichChange = { isOpen in - weakSelf?.repo.setPushEnable(isOpen) - } - model.cellModels.append(contentsOf: [messageNotify]) - return model - - + private func getSecondSection() -> SettingSectionModel { + let model = SettingSectionModel() + weak var weakSelf = self + let ringBellItem = SettingCellModel() + ringBellItem.cellName = NSLocalizedString("ring_mode", comment: "") + ringBellItem.type = SettingCellType.SettingSwitchCell.rawValue + ringBellItem.cornerType = .topLeft.union(.topRight) + ringBellItem.switchOpen = repo.getPushAudioEnable() + ringBellItem.swichChange = { isOpen in + weakSelf?.repo.setPushAudioEnable(isOpen) } - - private func getSecondSection() -> SettingSectionModel { - let model = SettingSectionModel() - weak var weakSelf = self - let ringBellItem = SettingCellModel() - ringBellItem.cellName = "响铃模式" - ringBellItem.type = SettingCellType.SettingSwitchCell.rawValue - ringBellItem.cornerType = .topLeft.union(.topRight) - ringBellItem.switchOpen = repo.getPushAudioEnable() - ringBellItem.swichChange = { isOpen in - weakSelf?.repo.setPushAudioEnable(isOpen) - } - - let vibrationItem = SettingCellModel() - vibrationItem.cellName = "震动模式" - vibrationItem.type = SettingCellType.SettingSwitchCell.rawValue - vibrationItem.cornerType = .bottomLeft.union(.bottomRight) - vibrationItem.switchOpen = repo.getPushShakeEnable() - vibrationItem.swichChange = { isOpen in - weakSelf?.repo.setPushShakeEnable(isOpen) - } - model.cellModels.append(contentsOf: [ringBellItem, vibrationItem]) - return model + + let vibrationItem = SettingCellModel() + vibrationItem.cellName = NSLocalizedString("vibration_mode", comment: "") + vibrationItem.type = SettingCellType.SettingSwitchCell.rawValue + vibrationItem.cornerType = .bottomLeft.union(.bottomRight) + vibrationItem.switchOpen = repo.getPushShakeEnable() + vibrationItem.swichChange = { isOpen in + weakSelf?.repo.setPushShakeEnable(isOpen) } - - private func getThirdSection() -> SettingSectionModel { - let model = SettingSectionModel() - weak var weakSelf = self - let receiveItem = SettingCellModel() - receiveItem.cellName = "PC/Web同步接收推送" - receiveItem.type = SettingCellType.SettingSwitchCell.rawValue - receiveItem.cornerType = .topLeft.union(.topRight) - receiveItem.switchOpen = repo.getPcWebPushEnable() - receiveItem.swichChange = { isOpen in - weakSelf?.repo.updatePcWebPushEnable(isOpen) - } - - let messageDetailItem = SettingCellModel() - messageDetailItem.cellName = "通知栏显示消息详情" - messageDetailItem.type = SettingCellType.SettingSwitchCell.rawValue - messageDetailItem.cornerType = .bottomLeft.union(.bottomRight) - messageDetailItem.switchOpen = repo.getPushDetailEnable() - messageDetailItem.swichChange = { isOpen in - weakSelf?.repo.settingProvider.setPushDetailEnable(isOpen) - } - - model.cellModels.append(contentsOf: [receiveItem, messageDetailItem]) - return model + model.cellModels.append(contentsOf: [ringBellItem, vibrationItem]) + return model + } + + private func getThirdSection() -> SettingSectionModel { + let model = SettingSectionModel() + weak var weakSelf = self +// let receiveItem = SettingCellModel() +// receiveItem.cellName = NSLocalizedString("syn_receive_push", comment: "") +// receiveItem.type = SettingCellType.SettingSwitchCell.rawValue +// receiveItem.cornerType = .topLeft.union(.topRight) +// receiveItem.switchOpen = repo.getPcWebPushEnable() +// receiveItem.swichChange = { isOpen in +// weakSelf?.repo.updatePcWebPushEnable(isOpen) +// } + + let messageDetailItem = SettingCellModel() + messageDetailItem.cellName = NSLocalizedString("display_message_detail", comment: "") + messageDetailItem.type = SettingCellType.SettingSwitchCell.rawValue + messageDetailItem.cornerType = .bottomLeft.union(.bottomRight) + messageDetailItem.switchOpen = repo.getPushDetailEnable() + messageDetailItem.swichChange = { isOpen in + weakSelf?.repo.settingProvider.setPushDetailEnable(isOpen) } - - + + model.cellModels.append(contentsOf: [messageDetailItem]) + return model + } } diff --git a/app/Mine/MineSettingViewController.swift b/app/Mine/MineSettingViewController.swift index e1e12123..af5ed881 100644 --- a/app/Mine/MineSettingViewController.swift +++ b/app/Mine/MineSettingViewController.swift @@ -9,7 +9,7 @@ import NEKitTeamUI import YXLogin import NIMSDK -class MineSettingViewController: NEBaseViewController,UITableViewDataSource, UITableViewDelegate { +class MineSettingViewController: NEBaseViewController, UITableViewDataSource, UITableViewDelegate { private var viewModel = MineSettingViewModel() public var cellClassDic = [ SettingCellType.SettingArrowCell.rawValue: TeamArrowSettingCell.self, @@ -30,7 +30,7 @@ class MineSettingViewController: NEBaseViewController,UITableViewDataSource, UIT } func initialConfig() { - title = "设置" + title = NSLocalizedString("setting", comment: "") viewModel.delegate = self } @@ -79,7 +79,7 @@ class MineSettingViewController: NEBaseViewController,UITableViewDataSource, UIT button.setTitle(title, for: .normal) button.addTarget(self, action: #selector(loginOutAction), for: .touchUpInside) button.layer.cornerRadius = 8.0 - button.setTitle("退出登录", for: .normal) + button.setTitle(NSLocalizedString("logout", comment: ""), for: .normal) NSLayoutConstraint.activate([ button.leftAnchor.constraint(equalTo: footer.leftAnchor, constant: 20), button.rightAnchor.constraint(equalTo: footer.rightAnchor, constant: -20), @@ -89,9 +89,7 @@ class MineSettingViewController: NEBaseViewController,UITableViewDataSource, UIT return footer } - //logout action @objc func loginOutAction(){ - NIMSDK.shared().loginManager.logout { error in NIMSDK.shared().qchatManager.logout { chatError in if error != nil { @@ -102,69 +100,69 @@ class MineSettingViewController: NEBaseViewController,UITableViewDataSource, UIT } } } + + } + // MARK: UITableViewDataSource, UITableViewDelegate + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if viewModel.sectionData.count > section { + let model = viewModel.sectionData[section] + return model.cellModels.count } - - //MARK: UITableViewDataSource, UITableViewDelegate - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if viewModel.sectionData.count > section { - let model = viewModel.sectionData[section] - return model.cellModels.count - } - return 0 - } + return 0 + } - func numberOfSections(in tableView: UITableView) -> Int { - viewModel.sectionData.count - } + func numberOfSections(in tableView: UITableView) -> Int { + viewModel.sectionData.count + } - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] - if let cell = tableView.dequeueReusableCell( - withIdentifier: "\(model.type)", - for: indexPath - ) as? BaseTeamSettingCell { - cell.configure(model) - return cell - } - return UITableViewCell() + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] + if let cell = tableView.dequeueReusableCell( + withIdentifier: "\(model.type)", + for: indexPath + ) as? BaseTeamSettingCell { + cell.configure(model) + return cell } + return UITableViewCell() + } - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] - if let block = model.cellClick { - block() - } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] + if let block = model.cellClick { + block() } + } - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] - return model.rowHeight - } + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] + return model.rowHeight + } - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - if viewModel.sectionData.count > section { - let model = viewModel.sectionData[section] - if model.cellModels.count > 0 { - return 12.0 - } + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + if viewModel.sectionData.count > section { + let model = viewModel.sectionData[section] + if model.cellModels.count > 0 { + return 12.0 } - return 0 } + return 0 + } - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let header = UIView() - header.backgroundColor = UIColor(hexString: "0xF1F1F6") - return header - } + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let header = UIView() + header.backgroundColor = UIColor(hexString: "0xF1F1F6") + return header + } - func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { - if section == viewModel.sectionData.count - 1 { - return 12.0 - } - return 0 + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + if section == viewModel.sectionData.count - 1 { + return 12.0 } + return 0 + } } extension MineSettingViewController: MineSettingViewModelDelegate { @@ -175,4 +173,3 @@ extension MineSettingViewController: MineSettingViewModelDelegate { func didClickCleanCache() {} } - diff --git a/app/Mine/MineSettingViewModel.swift b/app/Mine/MineSettingViewModel.swift index 197c7043..81fe4425 100644 --- a/app/Mine/MineSettingViewModel.swift +++ b/app/Mine/MineSettingViewModel.swift @@ -1,39 +1,37 @@ -// Copyright (c) 2022 NetEase, Inc. All rights reserved. -// Use of this source code is governed by a MIT license that can be found in the LICENSE file. +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. import Foundation import NEKitTeamUI protocol MineSettingViewModelDelegate: AnyObject { - func didMessageRemindClick() - func didClickCleanCache() + func didMessageRemindClick() + func didClickCleanCache() } public class MineSettingViewModel { - - var sectionData = [SettingSectionModel]() - weak var delegate: MineSettingViewModelDelegate? + var sectionData = [SettingSectionModel]() + weak var delegate: MineSettingViewModelDelegate? + public func getData() { + sectionData.append(getFirstSection()) + sectionData.append(getSecondSection()) + } - - public func getData(){ - sectionData.append(getFirstSection()) - sectionData.append(getSecondSection()) - } - - private func getFirstSection() -> SettingSectionModel { - let model = SettingSectionModel() - weak var weakSelf = self - let remind = SettingCellModel() - remind.cellName = "消息提醒" - remind.type = SettingCellType.SettingArrowCell.rawValue + private func getFirstSection() -> SettingSectionModel { + let model = SettingSectionModel() + weak var weakSelf = self + let remind = SettingCellModel() + remind.cellName = NSLocalizedString("message_remind", comment: "") + remind.type = SettingCellType.SettingArrowCell.rawValue // remind.cornerType = .topLeft.union(.topRight) - remind.cornerType = .topLeft.union(.topRight).union(.bottomLeft).union(.bottomRight) - remind.cellClick = { - weakSelf?.delegate?.didMessageRemindClick() - } - + remind.cornerType = .topLeft.union(.topRight).union(.bottomLeft).union(.bottomRight) + remind.cellClick = { + weakSelf?.delegate?.didMessageRemindClick() + } + // let cleanCache = SettingCellModel() // cleanCache.cellName = "清理缓存" // cleanCache.type = SettingCellType.SettingArrowCell.rawValue @@ -42,27 +40,25 @@ public class MineSettingViewModel { // weakSelf?.delegate?.didClickCleanCache() // } // model.cellModels.append(contentsOf: [remind, cleanCache]) - - model.cellModels.append(contentsOf: [remind]) - return model - } - - - private func getSecondSection() -> SettingSectionModel { - - let model = SettingSectionModel() - //听筒模式 - let receiverModel = SettingCellModel() - receiverModel.cellName = "听筒模式" - receiverModel.type = SettingCellType.SettingSwitchCell.rawValue - receiverModel.cornerType = .topLeft.union(.topRight) + model.cellModels.append(contentsOf: [remind]) + + return model + } + + private func getSecondSection() -> SettingSectionModel { + let model = SettingSectionModel() + // 听筒模式 + let receiverModel = SettingCellModel() + receiverModel.cellName = NSLocalizedString("receiver_mode", comment: "") + receiverModel.type = SettingCellType.SettingSwitchCell.rawValue + receiverModel.cornerType = .topLeft.union(.topRight) // receiverModel.switchOpen = CoreKitEngine.instance.repo.getHandSetMode() - receiverModel.switchOpen = IMKitEngine.instance.repo.getHandSetMode() + receiverModel.switchOpen = IMKitEngine.instance.repo.getHandSetMode() - receiverModel.swichChange = { isOpen in - IMKitEngine.instance.repo.setHandSetMode(isOpen) - } + receiverModel.swichChange = { isOpen in + IMKitEngine.instance.repo.setHandSetMode(isOpen) + } // //过滤通知 // let filterNotify = SettingCellModel() // filterNotify.cellName = "过滤通知" @@ -72,31 +68,28 @@ public class MineSettingViewModel { // filterNotify.swichChange = { isOpen in // // } - - //删除好友是否同步删除备注 - let deleteFriend = SettingCellModel() - deleteFriend.cellName = "删除好友是否同步删除备注" - deleteFriend.type = SettingCellType.SettingSwitchCell.rawValue - deleteFriend.switchOpen = IMKitEngine.instance.repo.getDeleteFriendAlias() - - deleteFriend.swichChange = { isOpen in - IMKitEngine.instance.repo.setDeleteFriendAlias(isOpen) - } - - //消息已读未读功能 - let hasRead = SettingCellModel() - hasRead.cellName = "消息已读未读功能" - hasRead.type = SettingCellType.SettingSwitchCell.rawValue - hasRead.cornerType = .bottomLeft.union(.bottomRight) + + // 删除好友是否同步删除备注 +// let deleteFriend = SettingCellModel() +// deleteFriend.cellName = NSLocalizedString("delete_friend", comment: "") +// deleteFriend.type = SettingCellType.SettingSwitchCell.rawValue +// deleteFriend.switchOpen = IMKitEngine.instance.repo.getDeleteFriendAlias() +// +// deleteFriend.swichChange = { isOpen in +// IMKitEngine.instance.repo.setDeleteFriendAlias(isOpen) +// } + + // 消息已读未读功能 + let hasRead = SettingCellModel() + hasRead.cellName = NSLocalizedString("message_read_function", comment: "") + hasRead.type = SettingCellType.SettingSwitchCell.rawValue + hasRead.cornerType = .bottomLeft.union(.bottomRight) // hasRead.switchOpen = true - hasRead.switchOpen = IMKitEngine.instance.repo.getMessageRead() - hasRead.swichChange = { isOpen in - IMKitEngine.instance.repo.setMessageRead(isOpen) - } - model.cellModels.append(contentsOf: [receiverModel ,deleteFriend,hasRead]) - return model - + hasRead.switchOpen = IMKitEngine.instance.repo.getMessageRead() + hasRead.swichChange = { isOpen in + IMKitEngine.instance.repo.setMessageRead(isOpen) } - - + model.cellModels.append(contentsOf: [receiverModel, hasRead]) + return model + } } diff --git a/app/Mine/MineTableViewCell.swift b/app/Mine/MineTableViewCell.swift index f06f395f..6ed50b6c 100644 --- a/app/Mine/MineTableViewCell.swift +++ b/app/Mine/MineTableViewCell.swift @@ -1,12 +1,12 @@ -// Copyright (c) 2022 NetEase, Inc. All rights reserved. -// Use of this source code is governed by a MIT license that can be found in the LICENSE file. +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. import UIKit - +import NEKitCommonUI public class MineTableViewCell: UITableViewCell { - // override func awakeFromNib() { // super.awakeFromNib() // // Initialization code @@ -17,84 +17,83 @@ public class MineTableViewCell: UITableViewCell { // // // Configure the view for the selected state // } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setUpSubViews() - } - - func setUpSubViews(){ - - self.selectionStyle = .none - self.contentView.addSubview(avatarImage) - self.contentView.addSubview(titleLabel) - self.contentView.addSubview(bottomLine) - self.contentView.addSubview(arrow) - - NSLayoutConstraint.activate([ - avatarImage.leftAnchor.constraint(equalTo: self.contentView.leftAnchor, constant: 20), - avatarImage.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor), - avatarImage.widthAnchor.constraint(equalToConstant: 20), - avatarImage.heightAnchor.constraint(equalToConstant: 20) - ]) - - NSLayoutConstraint.activate([ - titleLabel.leftAnchor.constraint(equalTo: avatarImage.rightAnchor, constant: 14), - titleLabel.centerYAnchor.constraint(equalTo: avatarImage.centerYAnchor), - ]) - - NSLayoutConstraint.activate([ - bottomLine.leftAnchor.constraint(equalTo: self.contentView.leftAnchor,constant: 20), - bottomLine.rightAnchor.constraint(equalTo: self.rightAnchor), - bottomLine.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor), - bottomLine.heightAnchor.constraint(equalToConstant: 0.5) - ]) - - NSLayoutConstraint.activate([ - arrow.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -25), + override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setUpSubViews() + } + + func setUpSubViews() { + selectionStyle = .none + contentView.addSubview(avatarImage) + contentView.addSubview(titleLabel) + contentView.addSubview(bottomLine) + contentView.addSubview(arrow) + + NSLayoutConstraint.activate([ + avatarImage.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20), + avatarImage.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + avatarImage.widthAnchor.constraint(equalToConstant: 20), + avatarImage.heightAnchor.constraint(equalToConstant: 20), + ]) + + NSLayoutConstraint.activate([ + titleLabel.leftAnchor.constraint(equalTo: avatarImage.rightAnchor, constant: 14), + titleLabel.centerYAnchor.constraint(equalTo: avatarImage.centerYAnchor), + ]) + + NSLayoutConstraint.activate([ + bottomLine.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20), + bottomLine.rightAnchor.constraint(equalTo: rightAnchor), + bottomLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + bottomLine.heightAnchor.constraint(equalToConstant: 0.5), + ]) + + NSLayoutConstraint.activate([ + arrow.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -25), // arrow.widthAnchor.constraint(equalToConstant: 15), - arrow.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor), - ]) - } + arrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + ]) + } - func configCell(data:[String:String]) { - titleLabel.text = data.keys.first - if let imageName = data.values.first { - avatarImage.image = UIImage.init(named: imageName) - } + func configCell(data: [String: String]) { + titleLabel.text = data.keys.first + if let imageName = data.values.first { + avatarImage.image = UIImage(named: imageName) } - - //MARK: lazy Method - public lazy var avatarImage: UIImageView = { - let avatar = UIImageView() - avatar.translatesAutoresizingMaskIntoConstraints = false - return avatar - }() - - public lazy var titleLabel: UILabel = { - let name = UILabel() - name.translatesAutoresizingMaskIntoConstraints = false - name.textColor = UIColor.ne_darkText - name.font = UIFont.systemFont(ofSize: 16.0) - name.text = "设置" - return name - }() - - private lazy var bottomLine:UIView = { - let view = UIView() - view.translatesAutoresizingMaskIntoConstraints = false - view.backgroundColor = UIColor.init(hexString: "0xDBE0E8") - return view - }() - - public lazy var arrow: UIImageView = { - let imageView = UIImageView(image:UIImage.init(named: "arrow_right")) - imageView.translatesAutoresizingMaskIntoConstraints = false - return imageView - }() + } + + // MARK: lazy Method + + public lazy var avatarImage: UIImageView = { + let avatar = UIImageView() + avatar.translatesAutoresizingMaskIntoConstraints = false + return avatar + }() + + public lazy var titleLabel: UILabel = { + let name = UILabel() + name.translatesAutoresizingMaskIntoConstraints = false + name.textColor = UIColor.ne_darkText + name.font = UIFont.systemFont(ofSize: 16.0) + name.text = NSLocalizedString("setting", comment: "") + return name + }() + + private lazy var bottomLine: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + view.backgroundColor = UIColor(hexString: "0xDBE0E8") + return view + }() + + public lazy var arrow: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "arrow_right")) + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() } diff --git a/app/Mine/NEAboutWebViewController.swift b/app/Mine/NEAboutWebViewController.swift index 9935e182..e44307b1 100644 --- a/app/Mine/NEAboutWebViewController.swift +++ b/app/Mine/NEAboutWebViewController.swift @@ -1,50 +1,47 @@ -// Copyright (c) 2022 NetEase, Inc. All rights reserved. -// Use of this source code is governed by a MIT license that can be found in the LICENSE file. +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. import UIKit import NEKitCore import WebKit +import NEKitCommonUI class NEAboutWebViewController: NEBaseViewController { + private var loadUrl: String = "" - private var loadUrl:String = "" - - override func viewDidLoad() { - super.viewDidLoad() - setUpSubViews() - } - - init(url:String) { - super.init(nibName: nil, bundle: nil) - loadUrl = url - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - - func setUpSubViews(){ - - self.title = "产品介绍" - guard let requestUrl = URL.init(string: loadUrl) else { - return - } - - let webview = WKWebView.init() - webview.translatesAutoresizingMaskIntoConstraints = false - let request = URLRequest.init(url:requestUrl ) - webview.load(request) - view.addSubview(webview) - - NSLayoutConstraint.activate([ - webview.leftAnchor.constraint(equalTo: view.leftAnchor), - webview.rightAnchor.constraint(equalTo: view.rightAnchor), - webview.topAnchor.constraint(equalTo: view.topAnchor), - webview.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) + override func viewDidLoad() { + super.viewDidLoad() + setUpSubViews() + } + + init(url: String) { + super.init(nibName: nil, bundle: nil) + loadUrl = url + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setUpSubViews() { + title = NSLocalizedString("product_intro", comment: "") + guard let requestUrl = URL(string: loadUrl) else { + return } + let webview = WKWebView() + webview.translatesAutoresizingMaskIntoConstraints = false + let request = URLRequest(url: requestUrl) + webview.load(request) + view.addSubview(webview) + NSLayoutConstraint.activate([ + webview.leftAnchor.constraint(equalTo: view.leftAnchor), + webview.rightAnchor.constraint(equalTo: view.rightAnchor), + webview.topAnchor.constraint(equalTo: view.topAnchor), + webview.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } } diff --git a/app/Mine/PersonInfoViewController.swift b/app/Mine/PersonInfoViewController.swift index 4884eb6e..284b7495 100644 --- a/app/Mine/PersonInfoViewController.swift +++ b/app/Mine/PersonInfoViewController.swift @@ -9,7 +9,9 @@ import NEKitTeamUI import NEKitChatUI import NIMSDK -class PersonInfoViewController: NEBaseViewController,NIMUserManagerDelegate,UINavigationControllerDelegate,PersonInfoViewModelDelegate,UITableViewDelegate, UITableViewDataSource { +class PersonInfoViewController: NEBaseViewController, NIMUserManagerDelegate, + UINavigationControllerDelegate, PersonInfoViewModelDelegate, UITableViewDelegate, + UITableViewDataSource { public var cellClassDic = [ SettingCellType.SettingSubtitleCell.rawValue: TeamSettingSubtitleCell.self, SettingCellType.SettingHeaderCell.rawValue: TeamSettingHeaderCell.self, @@ -31,7 +33,7 @@ class PersonInfoViewController: NEBaseViewController,NIMUserManagerDelegate,UINa } func initialConfig() { - title = "个人信息" + title = NSLocalizedString("person_info", comment: "") view.backgroundColor = UIColor(hexString: "0xF1F1F6") viewModel.delegate = self NIMSDK.shared().userManager.add(self) @@ -66,7 +68,8 @@ class PersonInfoViewController: NEBaseViewController,NIMUserManagerDelegate,UINa } second.setValue(UIColor(hexString: "0x333333"), forKey: "_titleTextColor") - let cancel = UIAlertAction(title: "取消", style: .cancel) { action in + let cancel = UIAlertAction(title: NSLocalizedString("cancel", comment: ""), + style: .cancel) { action in } cancel.setValue(UIColor(hexString: "0x333333"), forKey: "_titleTextColor") @@ -85,7 +88,7 @@ class PersonInfoViewController: NEBaseViewController,NIMUserManagerDelegate,UINa if let t = time { weakSelf?.viewModel.updateBirthday(birthDay: t) { error in if error != nil { - weakSelf?.showToast("设置生日失败") + weakSelf?.showToast(NSLocalizedString("setting_birthday_failure", comment: "")) } } } @@ -123,191 +126,198 @@ class PersonInfoViewController: NEBaseViewController,NIMUserManagerDelegate,UINa deinit { NIMSDK.shared().userManager.remove(self) } - //MARK: NIMUserManagerDelegate - func onUserInfoChanged(_ user: NIMUser) { - if user.userId == IMKitLoginManager.instance.imAccid { - viewModel.getData() - tableView.reloadData() - } - } - - - //MARK: UIImagePickerControllerDelegate - - func imagePickerController(_ picker: UIImagePickerController, - didFinishPickingMediaWithInfo info: [UIImagePickerController - .InfoKey: Any]) { - let image: UIImage = info[UIImagePickerController.InfoKey.editedImage] as! UIImage - uploadHeadImage(image: image) - dismiss(animated: true, completion: nil) + + // MARK: NIMUserManagerDelegate + + func onUserInfoChanged(_ user: NIMUser) { + if user.userId == IMKitEngine.instance.imAccid { + viewModel.getData() + tableView.reloadData() } + } - public func uploadHeadImage(image: UIImage) { - view.makeToastActivity(.center) - weak var weakSelf = self - if let imageData = image.jpegData(compressionQuality: 0.6) as NSData? { - let filePath = NSHomeDirectory().appending("/Documents/") - .appending(IMKitLoginManager.instance.imAccid) - let succcess = imageData.write(toFile: filePath, atomically: true) - if succcess { - NIMSDK.shared().resourceManager - .upload(filePath, scene: NIMNOSSceneTypeAvatar, - progress: nil) { urlString, error in - if error == nil { - weakSelf?.viewModel.updateAvatar(avatar: urlString ?? "") { error in - if error != nil { - weakSelf?.showToast("修改头像失败失败") - } - } + // MARK: UIImagePickerControllerDelegate + + func imagePickerController(_ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController + .InfoKey: Any]) { + let image: UIImage = info[UIImagePickerController.InfoKey.editedImage] as! UIImage + uploadHeadImage(image: image) + dismiss(animated: true, completion: nil) + } - } else { - NELog.errorLog( - weakSelf?.className ?? "", - desc: "❌upload image failed,error = \(error!)" - ) + public func uploadHeadImage(image: UIImage) { + view.makeToastActivity(.center) + weak var weakSelf = self + if let imageData = image.jpegData(compressionQuality: 0.6) as NSData? { + let filePath = NSHomeDirectory().appending("/Documents/") + .appending(IMKitEngine.instance.imAccid) + let succcess = imageData.write(toFile: filePath, atomically: true) + if succcess { + NIMSDK.shared().resourceManager + .upload(filePath, scene: NIMNOSSceneTypeAvatar, + progress: nil) { urlString, error in + if error == nil { + weakSelf?.viewModel.updateAvatar(avatar: urlString ?? "") { error in + if error != nil { + weakSelf?.showToast(NSLocalizedString("setting_head_failure", comment: "")) + } } - self.view.hideToastActivity() + + } else { + NELog.errorLog( + weakSelf?.className ?? "", + desc: "❌upload image failed,error = \(error!)" + ) } - } + self.view.hideToastActivity() + } } } - //MARK: PersonInfoViewModelDelegate - func didClickHeadImage() { - showBottomAlert(self) - } + } - func didClickNickName(name: String) { - let ctrl = InputPersonInfoController() - ctrl.configTitle(editType: .nickName) - ctrl.contentText = name - weak var weakSelf = self - ctrl.callBack = { editText in - weakSelf?.viewModel.updateNickName(name: editText) { error in - if error != nil { - weakSelf?.showToast("修改昵称失败") - } + // MARK: PersonInfoViewModelDelegate + + func didClickHeadImage() { + showBottomAlert(self) + } + + func didClickNickName(name: String) { + let ctrl = InputPersonInfoController() + ctrl.configTitle(editType: .nickName) + ctrl.contentText = name + weak var weakSelf = self + ctrl.callBack = { editText in + weakSelf?.viewModel.updateNickName(name: editText) { error in + if error != nil { + weakSelf?.showToast(NSLocalizedString("setting_nickname_failure", comment: "")) } } - navigationController?.pushViewController(ctrl, animated: true) } + navigationController?.pushViewController(ctrl, animated: true) + } - func didClickGender() { - var sex = NIMUserGender.unknown - weak var weakSelf = self - showAlert(firstContent: "男", secondContent: "女") { value in - sex = value == 0 ? .male : .female - weakSelf?.viewModel.updateSex(sex: sex) { error in - if error != nil { - weakSelf?.showToast("修改性别失败") - } + func didClickGender() { + var sex = NIMUserGender.unknown + weak var weakSelf = self + showAlert( + firstContent: NSLocalizedString("male", comment: ""), + secondContent: NSLocalizedString("female", comment: "") + ) { value in + sex = value == 0 ? .male : .female + weakSelf?.viewModel.updateSex(sex: sex) { error in + if error != nil { + weakSelf?.showToast(NSLocalizedString("change_gender_failure", comment: "")) } } } + } - func didClickBirthday() { - showDatePicker() - } + func didClickBirthday() { + showDatePicker() + } - func didClickMobile(mobile: String) { - let ctrl = InputPersonInfoController() - ctrl.configTitle(editType: .cellphone) - ctrl.contentText = mobile - weak var weakSelf = self - ctrl.callBack = { editText in - weakSelf?.viewModel.updateMobile(mobile: editText) { error in - if error != nil { - weakSelf?.showToast("修改手机号失败") - } + func didClickMobile(mobile: String) { + let ctrl = InputPersonInfoController() + ctrl.configTitle(editType: .cellphone) + ctrl.contentText = mobile + weak var weakSelf = self + ctrl.callBack = { editText in + weakSelf?.viewModel.updateMobile(mobile: editText) { error in + if error != nil { + weakSelf?.showToast(NSLocalizedString("change_phone_failure", comment: "")) } } - navigationController?.pushViewController(ctrl, animated: true) } + navigationController?.pushViewController(ctrl, animated: true) + } - func didClickEmail(email: String) { - let ctrl = InputPersonInfoController() - ctrl.configTitle(editType: .email) - ctrl.contentText = email - weak var weakSelf = self - ctrl.callBack = { editText in - weakSelf?.viewModel.updateEmail(email: editText) { error in - if error != nil { - weakSelf?.showToast("修改邮箱失败") - } + func didClickEmail(email: String) { + let ctrl = InputPersonInfoController() + ctrl.configTitle(editType: .email) + ctrl.contentText = email + weak var weakSelf = self + ctrl.callBack = { editText in + weakSelf?.viewModel.updateEmail(email: editText) { error in + if error != nil { + weakSelf?.showToast(NSLocalizedString("change_email_failure", comment: "")) } } - navigationController?.pushViewController(ctrl, animated: true) } + navigationController?.pushViewController(ctrl, animated: true) + } - func didClickSign(sign: String) { - let ctrl = InputPersonInfoController() - ctrl.configTitle(editType: .specialSign) - ctrl.contentText = sign - weak var weakSelf = self - ctrl.callBack = { editText in - weakSelf?.viewModel.updateSign(sign: editText) { error in - if error != nil { - weakSelf?.showToast("修改签名失败") - } + func didClickSign(sign: String) { + let ctrl = InputPersonInfoController() + ctrl.configTitle(editType: .specialSign) + ctrl.contentText = sign + weak var weakSelf = self + ctrl.callBack = { editText in + weakSelf?.viewModel.updateSign(sign: editText) { error in + if error != nil { + weakSelf?.showToast(NSLocalizedString("change_sign_failure", comment: "")) } } - navigationController?.pushViewController(ctrl, animated: true) } + navigationController?.pushViewController(ctrl, animated: true) + } - func didCopyAccount(account: String) { - showToast("复制成功") - UIPasteboard.general.string = account - } - //MARK: UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if viewModel.sectionData.count > section { - let model = viewModel.sectionData[section] - return model.cellModels.count - } - return 0 - } + func didCopyAccount(account: String) { + showToast(NSLocalizedString("copy_success", comment: "")) + UIPasteboard.general.string = account + } - func numberOfSections(in tableView: UITableView) -> Int { - viewModel.sectionData.count - } + // MARK: UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] - if let cell = tableView.dequeueReusableCell( - withIdentifier: "\(model.type)", - for: indexPath - ) as? BaseTeamSettingCell { - cell.configure(model) - return cell - } - return UITableViewCell() + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if viewModel.sectionData.count > section { + let model = viewModel.sectionData[section] + return model.cellModels.count } + return 0 + } - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] - if let block = model.cellClick { - block() - } + func numberOfSections(in tableView: UITableView) -> Int { + viewModel.sectionData.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] + if let cell = tableView.dequeueReusableCell( + withIdentifier: "\(model.type)", + for: indexPath + ) as? BaseTeamSettingCell { + cell.configure(model) + return cell } + return UITableViewCell() + } - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] - return model.rowHeight + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] + if let block = model.cellClick { + block() } + } - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - if viewModel.sectionData.count > section { - let model = viewModel.sectionData[section] - if model.cellModels.count > 0 { - return 12.0 - } + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + let model = viewModel.sectionData[indexPath.section].cellModels[indexPath.row] + return model.rowHeight + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + if viewModel.sectionData.count > section { + let model = viewModel.sectionData[section] + if model.cellModels.count > 0 { + return 12.0 } - return 0 } + return 0 + } - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let header = UIView() - header.backgroundColor = UIColor(hexString: "0xF1F1F6") - return header - } + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let header = UIView() + header.backgroundColor = UIColor(hexString: "0xF1F1F6") + return header + } } - diff --git a/app/Mine/PersonInfoViewModel.swift b/app/Mine/PersonInfoViewModel.swift index f21c6d10..1653f41e 100644 --- a/app/Mine/PersonInfoViewModel.swift +++ b/app/Mine/PersonInfoViewModel.swift @@ -1,234 +1,233 @@ -// Copyright (c) 2022 NetEase, Inc. All rights reserved. -// Use of this source code is governed by a MIT license that can be found in the LICENSE file. +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. import Foundation import NEKitTeamUI import NIMSDK protocol PersonInfoViewModelDelegate: AnyObject { - func didClickHeadImage() - func didClickNickName(name:String) - func didClickGender() - func didClickBirthday() - func didClickMobile(mobile:String) - func didClickEmail(email:String) - func didClickSign(sign:String) - func didCopyAccount(account:String) + func didClickHeadImage() + func didClickNickName(name: String) + func didClickGender() + func didClickBirthday() + func didClickMobile(mobile: String) + func didClickEmail(email: String) + func didClickSign(sign: String) + func didCopyAccount(account: String) } -public class PersonInfoViewModel{ - - var sectionData = [SettingSectionModel]() - public let friendProvider = FriendProvider.shared - public let userProvider = UserInfoProvider.shared - - private var userInfo:User? - weak var delegate:PersonInfoViewModelDelegate? - - func getData(){ - sectionData.removeAll() - userInfo = userProvider.getUserInfo(userId: IMKitLoginManager.instance.imAccid) - sectionData.append(getFirstSection()) - sectionData.append(getSecondSection()) - } - - - private func getFirstSection() -> SettingSectionModel { - let model = SettingSectionModel() - - guard let mineInfo = userInfo else { - return model - } - - weak var weakSelf = self - let headImageItem = SettingCellModel() - headImageItem.cornerType = .topLeft.union(.topRight) - headImageItem.type = SettingCellType.SettingHeaderCell.rawValue - headImageItem.cellName = "头像" - headImageItem.headerUrl = userInfo?.userInfo?.avatarUrl - headImageItem.defaultHeadData = userInfo?.showName() - headImageItem.rowHeight = 64.0 - headImageItem.cellClick = { - weakSelf?.delegate?.didClickHeadImage() - } - - - //昵称 - let nickNameItem = SettingCellModel() - nickNameItem.type = SettingCellType.SettingSubtitleCell.rawValue - nickNameItem.cellName = "昵称" - nickNameItem.subTitle = mineInfo.showName() - nickNameItem.rowHeight = 46.0 - nickNameItem.cellClick = { - weakSelf?.delegate?.didClickNickName(name: nickNameItem.subTitle ?? "") - } - - //账号 - let accountItem = SettingCellModel() - accountItem.type = SettingCellType.SettingSubtitleCustomCell.rawValue - accountItem.cellName = "账号" - accountItem.subTitle = mineInfo.userId - accountItem.rowHeight = 46.0 - accountItem.rightCustomViewIcon = "copy_icon" - accountItem.customViewClick = { - weakSelf?.delegate?.didCopyAccount(account: mineInfo.userId ?? "") - } - - //性别 - let sexItem = SettingCellModel() - sexItem.type = SettingCellType.SettingSubtitleCell.rawValue - sexItem.cellName = "性别" - var sex = "未知" - switch mineInfo.userInfo?.gender { - case .male: - sex = "男" - break - case .female: - sex = "女" - break - default: - sex = "未知" - } - sexItem.subTitle = sex - sexItem.rowHeight = 46.0 - sexItem.cellClick = { - weakSelf?.delegate?.didClickGender() - } - - //生日 - let birthdayItem = SettingCellModel() - birthdayItem.type = SettingCellType.SettingSubtitleCell.rawValue - birthdayItem.cellName = "生日" - birthdayItem.subTitle = mineInfo.userInfo?.birth - birthdayItem.rowHeight = 46.0 - birthdayItem.cellClick = { - weakSelf?.delegate?.didClickBirthday() - } - //手机 - let telephoneItem = SettingCellModel() - telephoneItem.type = SettingCellType.SettingSubtitleCell.rawValue - telephoneItem.cellName = "手机" - telephoneItem.subTitle = mineInfo.userInfo?.mobile - telephoneItem.rowHeight = 46.0 - telephoneItem.cellClick = { - weakSelf?.delegate?.didClickMobile(mobile: telephoneItem.subTitle ?? "") - } - - //邮箱 - let emailItem = SettingCellModel() - emailItem.type = SettingCellType.SettingSubtitleCell.rawValue - emailItem.cellName = "邮箱" - emailItem.subTitle = mineInfo.userInfo?.email - emailItem.cornerType = .bottomLeft.union(.bottomRight) - emailItem.rowHeight = 46.0 - emailItem.cellClick = { - weakSelf?.delegate?.didClickEmail(email: emailItem.subTitle ?? "") - } - model.cellModels.append(contentsOf: [headImageItem, nickNameItem,accountItem,sexItem,birthdayItem,telephoneItem,emailItem]) - return model - - } - - private func getSecondSection() -> SettingSectionModel { - - let model = SettingSectionModel() - guard let mineInfo = userInfo else { - return model - } - - - let signItem = SettingCellModel() - signItem.type = SettingCellType.SettingSubtitleCell.rawValue - signItem.cellName = "个性签名" - signItem.subTitle = mineInfo.userInfo?.sign - signItem.rowHeight = 46.0 - signItem.cornerType = .topLeft.union(.topRight).union(.bottomLeft).union(.bottomRight) - weak var weakSelf = self - signItem.cellClick = { - weakSelf?.delegate?.didClickSign(sign: signItem.subTitle ?? "") - } - model.cellModels.append(contentsOf: [signItem]) - return model - } - - - func updateAvatar(avatar:String,_ completion:@escaping (NSError?)->Void) { - let changeValue = [NSNumber.init(value: NIMUserInfoUpdateTag.avatar.rawValue):avatar] - userProvider.updateMyUserInfo(values: changeValue) { error in - if error == nil { - completion(nil) - }else { - completion(error) - } - } - } - - func updateSex(sex:NIMUserGender,_ completion:@escaping (NSError?)->Void) { - let changeValue = [NSNumber.init(value: NIMUserInfoUpdateTag.gender.rawValue):NSNumber.init(value: sex.rawValue)] - userProvider.updateMyUserInfo(values: changeValue) { error in - if error == nil { - completion(nil) - }else { - completion(error) - } - } - } - - - func updateBirthday(birthDay:String,_ completion:@escaping (NSError?)->Void) { - let changeValue = [NSNumber.init(value: NIMUserInfoUpdateTag.birth.rawValue):birthDay] - userProvider.updateMyUserInfo(values: changeValue) { error in - if error == nil { - completion(nil) - }else { - completion(error) - } - } - } - - - func updateNickName(name:String,_ completion:@escaping (NSError?)->Void) { - let changeValue = [NSNumber.init(value: NIMUserInfoUpdateTag.nick.rawValue):name] - userProvider.updateMyUserInfo(values: changeValue) { error in - if error == nil { - completion(nil) - }else { - completion(error) - } - } - } - - func updateMobile(mobile:String,_ completion:@escaping (NSError?)->Void) { - let changeValue = [NSNumber.init(value: NIMUserInfoUpdateTag.mobile.rawValue):mobile] - userProvider.updateMyUserInfo(values: changeValue) { error in - if error == nil { - completion(nil) - }else { - completion(error) - } - } - } - - func updateEmail(email:String,_ completion:@escaping (NSError?)->Void) { - let changeValue = [NSNumber.init(value: NIMUserInfoUpdateTag.email.rawValue):email] - userProvider.updateMyUserInfo(values: changeValue) { error in - if error == nil { - completion(nil) - }else { - completion(error) - } - } - } - - func updateSign(sign:String,_ completion:@escaping (NSError?)->Void) { - let changeValue = [NSNumber.init(value: NIMUserInfoUpdateTag.sign.rawValue):sign] - userProvider.updateMyUserInfo(values: changeValue) { error in - if error == nil { - completion(nil) - }else { - completion(error) - } - } +public class PersonInfoViewModel { + var sectionData = [SettingSectionModel]() + public let friendProvider = FriendProvider.shared + public let userProvider = UserInfoProvider.shared + + private var userInfo: User? + weak var delegate: PersonInfoViewModelDelegate? + + func getData() { + sectionData.removeAll() + userInfo = userProvider.getUserInfo(userId: IMKitEngine.instance.imAccid) + sectionData.append(getFirstSection()) + sectionData.append(getSecondSection()) + } + + private func getFirstSection() -> SettingSectionModel { + let model = SettingSectionModel() + + guard let mineInfo = userInfo else { + return model + } + + weak var weakSelf = self + let headImageItem = SettingCellModel() + headImageItem.cornerType = .topLeft.union(.topRight) + headImageItem.type = SettingCellType.SettingHeaderCell.rawValue + headImageItem.cellName = NSLocalizedString("headImage", comment: "") + headImageItem.headerUrl = userInfo?.userInfo?.avatarUrl + headImageItem.defaultHeadData = userInfo?.showName() + headImageItem.rowHeight = 64.0 + headImageItem.cellClick = { + weakSelf?.delegate?.didClickHeadImage() + } + + // 昵称 + let nickNameItem = SettingCellModel() + nickNameItem.type = SettingCellType.SettingSubtitleCell.rawValue + nickNameItem.cellName = NSLocalizedString("nickname", comment: "") + nickNameItem.subTitle = mineInfo.showName() + nickNameItem.rowHeight = 46.0 + nickNameItem.cellClick = { + weakSelf?.delegate?.didClickNickName(name: nickNameItem.subTitle ?? "") + } + + // 账号 + let accountItem = SettingCellModel() + accountItem.type = SettingCellType.SettingSubtitleCustomCell.rawValue + accountItem.cellName = NSLocalizedString("account", comment: "") + accountItem.subTitle = mineInfo.userId + accountItem.rowHeight = 46.0 + accountItem.rightCustomViewIcon = "copy_icon" + accountItem.customViewClick = { + weakSelf?.delegate?.didCopyAccount(account: mineInfo.userId ?? "") + } + + // 性别 + let sexItem = SettingCellModel() + sexItem.type = SettingCellType.SettingSubtitleCell.rawValue + sexItem.cellName = NSLocalizedString("gender", comment: "") + var sex = NSLocalizedString("unknown", comment: "") + switch mineInfo.userInfo?.gender { + case .male: + sex = NSLocalizedString("male", comment: "") + case .female: + sex = NSLocalizedString("female", comment: "") + default: + sex = NSLocalizedString("unknown", comment: "") + } + sexItem.subTitle = sex + sexItem.rowHeight = 46.0 + sexItem.cellClick = { + weakSelf?.delegate?.didClickGender() + } + + // 生日 + let birthdayItem = SettingCellModel() + birthdayItem.type = SettingCellType.SettingSubtitleCell.rawValue + birthdayItem.cellName = NSLocalizedString("birehday", comment: "") + birthdayItem.subTitle = mineInfo.userInfo?.birth + birthdayItem.rowHeight = 46.0 + birthdayItem.cellClick = { + weakSelf?.delegate?.didClickBirthday() + } + // 手机 + let telephoneItem = SettingCellModel() + telephoneItem.type = SettingCellType.SettingSubtitleCell.rawValue + telephoneItem.cellName = NSLocalizedString("phone", comment: "") + telephoneItem.subTitle = mineInfo.userInfo?.mobile + telephoneItem.rowHeight = 46.0 + telephoneItem.cellClick = { + weakSelf?.delegate?.didClickMobile(mobile: telephoneItem.subTitle ?? "") + } + + // 邮箱 + let emailItem = SettingCellModel() + emailItem.type = SettingCellType.SettingSubtitleCell.rawValue + emailItem.cellName = NSLocalizedString("email", comment: "") + emailItem.subTitle = mineInfo.userInfo?.email + emailItem.cornerType = .bottomLeft.union(.bottomRight) + emailItem.rowHeight = 46.0 + emailItem.cellClick = { + weakSelf?.delegate?.didClickEmail(email: emailItem.subTitle ?? "") + } + model.cellModels.append(contentsOf: [ + headImageItem, + nickNameItem, + accountItem, + sexItem, + birthdayItem, + telephoneItem, + emailItem, + ]) + return model + } + + private func getSecondSection() -> SettingSectionModel { + let model = SettingSectionModel() + guard let mineInfo = userInfo else { + return model + } + + let signItem = SettingCellModel() + signItem.type = SettingCellType.SettingSubtitleCell.rawValue + signItem.cellName = NSLocalizedString("individuality_sign", comment: "") + signItem.subTitle = mineInfo.userInfo?.sign + signItem.rowHeight = 46.0 + signItem.cornerType = .topLeft.union(.topRight).union(.bottomLeft).union(.bottomRight) + weak var weakSelf = self + signItem.cellClick = { + weakSelf?.delegate?.didClickSign(sign: signItem.subTitle ?? "") + } + model.cellModels.append(contentsOf: [signItem]) + return model + } + + func updateAvatar(avatar: String, _ completion: @escaping (NSError?) -> Void) { + let changeValue = [NSNumber(value: NIMUserInfoUpdateTag.avatar.rawValue): avatar] + userProvider.updateMyUserInfo(values: changeValue) { error in + if error == nil { + completion(nil) + } else { + completion(error) + } + } + } + + func updateSex(sex: NIMUserGender, _ completion: @escaping (NSError?) -> Void) { + let changeValue = + [NSNumber(value: NIMUserInfoUpdateTag.gender.rawValue): NSNumber(value: sex.rawValue)] + userProvider.updateMyUserInfo(values: changeValue) { error in + if error == nil { + completion(nil) + } else { + completion(error) + } + } + } + + func updateBirthday(birthDay: String, _ completion: @escaping (NSError?) -> Void) { + let changeValue = [NSNumber(value: NIMUserInfoUpdateTag.birth.rawValue): birthDay] + userProvider.updateMyUserInfo(values: changeValue) { error in + if error == nil { + completion(nil) + } else { + completion(error) + } + } + } + + func updateNickName(name: String, _ completion: @escaping (NSError?) -> Void) { + let changeValue = [NSNumber(value: NIMUserInfoUpdateTag.nick.rawValue): name] + userProvider.updateMyUserInfo(values: changeValue) { error in + if error == nil { + completion(nil) + } else { + completion(error) + } + } + } + + func updateMobile(mobile: String, _ completion: @escaping (NSError?) -> Void) { + let changeValue = [NSNumber(value: NIMUserInfoUpdateTag.mobile.rawValue): mobile] + userProvider.updateMyUserInfo(values: changeValue) { error in + if error == nil { + completion(nil) + } else { + completion(error) + } + } + } + + func updateEmail(email: String, _ completion: @escaping (NSError?) -> Void) { + let changeValue = [NSNumber(value: NIMUserInfoUpdateTag.email.rawValue): email] + userProvider.updateMyUserInfo(values: changeValue) { error in + if error == nil { + completion(nil) + } else { + completion(error) + } + } + } + + func updateSign(sign: String, _ completion: @escaping (NSError?) -> Void) { + let changeValue = [NSNumber(value: NIMUserInfoUpdateTag.sign.rawValue): sign] + userProvider.updateMyUserInfo(values: changeValue) { error in + if error == nil { + completion(nil) + } else { + completion(error) + } } + } } diff --git a/app/Mine/VersionCell.swift b/app/Mine/VersionCell.swift index 13ef29aa..788a7005 100644 --- a/app/Mine/VersionCell.swift +++ b/app/Mine/VersionCell.swift @@ -1,113 +1,112 @@ -// Copyright (c) 2022 NetEase, Inc. All rights reserved. -// Use of this source code is governed by a MIT license that can be found in the LICENSE file. +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. import UIKit import NEKitTeamUI -enum IntroduceCellType:Int { - case version = 0 - case productIntroduce +enum IntroduceCellType: Int { + case version = 0 + case productIntroduce } class VersionCell: UITableViewCell { - - public var cellType:IntroduceCellType? { - didSet { - if cellType == .version { - subTitle.isHidden = false - arrow.isHidden = true - }else { - subTitle.isHidden = true - arrow.isHidden = false - } - } - } - - override func awakeFromNib() { - super.awakeFromNib() - // Initialization code + public var cellType: IntroduceCellType? { + didSet { + if cellType == .version { + subTitle.isHidden = false + arrow.isHidden = true + } else { + subTitle.isHidden = true + arrow.isHidden = false + } } + } - override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } - // Configure the view for the selected state - } + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupSubviews() - self.selectionStyle = .none - } - - func setupSubviews(){ - contentView.addSubview(titleLabel) - contentView.addSubview(subTitle) - contentView.addSubview(arrow) - contentView.addSubview(bottomLine) - - NSLayoutConstraint.activate([ - titleLabel.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20), - titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - ]) - - NSLayoutConstraint.activate([ - subTitle.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20), - subTitle.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - ]) - - NSLayoutConstraint.activate([ - arrow.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20), - arrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - ]) - - NSLayoutConstraint.activate([ - bottomLine.leftAnchor.constraint(equalTo: contentView.leftAnchor,constant: 20), - bottomLine.rightAnchor.constraint(equalTo: rightAnchor), - bottomLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - bottomLine.heightAnchor.constraint(equalToConstant: 0.5) - ]) + // Configure the view for the selected state + } - } - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func configData(model:SettingCellModel){ - titleLabel.text = model.cellName - subTitle.text = model.subTitle - } - - lazy var titleLabel: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.textColor = UIColor.init(hexString: "0x333333") - label.font = UIFont.systemFont(ofSize: 14) - return label - }() - - lazy var subTitle: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.textColor = UIColor.init(hexString: "0x333333") - label.font = UIFont.systemFont(ofSize: 14) - label.isHidden = true - return label - }() - - public lazy var arrow: UIImageView = { - let imageView = UIImageView(image:UIImage.init(named: "arrow_right")) - imageView.translatesAutoresizingMaskIntoConstraints = false - imageView.isHidden = true - return imageView - }() - - - private lazy var bottomLine:UIView = { - let view = UIView() - view.translatesAutoresizingMaskIntoConstraints = false - view.backgroundColor = UIColor.init(hexString: "0xDBE0E8") - return view - }() + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupSubviews() + selectionStyle = .none + } + + func setupSubviews() { + contentView.addSubview(titleLabel) + contentView.addSubview(subTitle) + contentView.addSubview(arrow) + contentView.addSubview(bottomLine) + + NSLayoutConstraint.activate([ + titleLabel.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20), + titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + ]) + + NSLayoutConstraint.activate([ + subTitle.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20), + subTitle.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + ]) + + NSLayoutConstraint.activate([ + arrow.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20), + arrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + ]) + + NSLayoutConstraint.activate([ + bottomLine.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20), + bottomLine.rightAnchor.constraint(equalTo: rightAnchor), + bottomLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + bottomLine.heightAnchor.constraint(equalToConstant: 0.5), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configData(model: SettingCellModel) { + titleLabel.text = model.cellName + subTitle.text = model.subTitle + } + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.textColor = UIColor(hexString: "0x333333") + label.font = UIFont.systemFont(ofSize: 14) + return label + }() + + lazy var subTitle: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.textColor = UIColor(hexString: "0x333333") + label.font = UIFont.systemFont(ofSize: 14) + label.isHidden = true + return label + }() + + public lazy var arrow: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "arrow_right")) + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.isHidden = true + return imageView + }() + + private lazy var bottomLine: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + view.backgroundColor = UIColor(hexString: "0xDBE0E8") + return view + }() } From b1b2fed5e24dd8ab58a053ea6d1bbc75ff4d1602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=96=84=E6=A0=8B?= Date: Mon, 19 Sep 2022 16:21:19 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=A3=B3=E5=AD=90?= =?UTF-8?q?=E5=B7=A5=E7=A8=8Bicon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AppIcon.appiconset/58x58-2.png | Bin 0 -> 3956 bytes .../Contents.json | 4 +- .../launchIcon.imageset/launchIcon@2x.png | Bin 0 -> 7144 bytes .../launchIcon.imageset/launchIcon@3x.png | Bin 0 -> 45485 bytes .../launch_icon.imageset/launch_icon@2x.png | Bin 24131 -> 0 bytes .../launch_icon.imageset/launch_icon@3x.png | Bin 47287 -> 0 bytes .../yunxin_brand.imageset/Contents.json | 22 +++ .../yunxin_brand.imageset/yunxin_brand@2x.png | Bin 0 -> 1309 bytes .../yunxin_brand.imageset/yunxin_brand@3x.png | Bin 0 -> 1839 bytes .../yunxin_logo.imageset/yunxin_logo@2x.png | Bin 4056 -> 3904 bytes .../yunxin_logo.imageset/yunxin_logo@3x.png | Bin 3183 -> 7070 bytes app/NENavigationController.swift | 39 ++-- app/NETabBarController.swift | 187 ++++++++++-------- app/ViewController.swift | 154 ++++++++------- app/en.lproj/InfoPlist.strings | 10 + app/en.lproj/Localizable.strings | 58 ++++++ 16 files changed, 305 insertions(+), 169 deletions(-) create mode 100644 app/Assets.xcassets/AppIcon.appiconset/58x58-2.png rename app/Assets.xcassets/{launch_icon.imageset => launchIcon.imageset}/Contents.json (76%) create mode 100644 app/Assets.xcassets/launchIcon.imageset/launchIcon@2x.png create mode 100644 app/Assets.xcassets/launchIcon.imageset/launchIcon@3x.png delete mode 100644 app/Assets.xcassets/launch_icon.imageset/launch_icon@2x.png delete mode 100644 app/Assets.xcassets/launch_icon.imageset/launch_icon@3x.png create mode 100644 app/Assets.xcassets/yunxin_brand.imageset/Contents.json create mode 100644 app/Assets.xcassets/yunxin_brand.imageset/yunxin_brand@2x.png create mode 100644 app/Assets.xcassets/yunxin_brand.imageset/yunxin_brand@3x.png create mode 100644 app/en.lproj/InfoPlist.strings create mode 100644 app/en.lproj/Localizable.strings diff --git a/app/Assets.xcassets/AppIcon.appiconset/58x58-2.png b/app/Assets.xcassets/AppIcon.appiconset/58x58-2.png new file mode 100644 index 0000000000000000000000000000000000000000..84f6412561852e2087a61d3e9092dd0cdc0bf24c GIT binary patch literal 3956 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 literal 0 HcmV?d00001 diff --git a/app/Assets.xcassets/launch_icon.imageset/Contents.json b/app/Assets.xcassets/launchIcon.imageset/Contents.json similarity index 76% rename from app/Assets.xcassets/launch_icon.imageset/Contents.json rename to app/Assets.xcassets/launchIcon.imageset/Contents.json index 1063270e..4b3f8bdc 100644 --- a/app/Assets.xcassets/launch_icon.imageset/Contents.json +++ b/app/Assets.xcassets/launchIcon.imageset/Contents.json @@ -5,12 +5,12 @@ "scale" : "1x" }, { - "filename" : "launch_icon@2x.png", + "filename" : "launchIcon@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "launch_icon@3x.png", + "filename" : "launchIcon@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/app/Assets.xcassets/launchIcon.imageset/launchIcon@2x.png b/app/Assets.xcassets/launchIcon.imageset/launchIcon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3b7a373f0c571b2fefe9e44bc142aa4733bd090e GIT binary patch literal 7144 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 D@UtkK literal 0 HcmV?d00001 diff --git a/app/Assets.xcassets/launch_icon.imageset/launch_icon@2x.png b/app/Assets.xcassets/launch_icon.imageset/launch_icon@2x.png deleted file mode 100644 index ed120837064bb46e3004286f9366a17d30fa9115..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24131 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 diff --git a/app/Assets.xcassets/yunxin_brand.imageset/Contents.json b/app/Assets.xcassets/yunxin_brand.imageset/Contents.json new file mode 100644 index 00000000..90e0b26d --- /dev/null +++ b/app/Assets.xcassets/yunxin_brand.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "yunxin_brand@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "yunxin_brand@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/Assets.xcassets/yunxin_brand.imageset/yunxin_brand@2x.png b/app/Assets.xcassets/yunxin_brand.imageset/yunxin_brand@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..530f576a43bf78e5a85bb6f378c1fd81d3bcede8 GIT binary patch literal 1309 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 literal 0 HcmV?d00001 diff --git a/app/Assets.xcassets/yunxin_logo.imageset/yunxin_logo@2x.png b/app/Assets.xcassets/yunxin_logo.imageset/yunxin_logo@2x.png index 191c4e2b43240abda90f22d2c6ce82efad20c6f0..c989ac499b065901f71a277a908849b0bce457a0 100644 GIT binary patch delta 3891 zcmV-356tk`AHW_ViBL{Q4GJ0x0000DNk~Le0000_0000w2nGNE0P))d^pPPne-712 zL_t(|0qtDfi(OY0U+dg^C%2iHj0GPhlfR%-q@Yi^c~MHKO)ZLuk{MsDFD3y6!H1a< zL7|E>TC9kenIeiHHhn17B4qL)#Scsh;)5cc2;zfCZqrQW&b{aCrqKXK(=~0!0C^NCQTz&wD!zoUHo{f7C$NU~9i$ z0=YiS&s?Dym21XX}-~kDc1KuC_V+<+@xB}v!p$_F;FHIbbj(ShEuLe^Wbdh7%6^%(5FpW`%7XEv6st*ef!o&lE z*M5ag$L;=j=zsF}e_TTZSQ>SnVtNA-7cAZR!2SSE|C$GbJaBu07Pg(Xy|MLCJO@XV zk>E|gMsu8fJjEj@MSwZY(|B>tilh#1@{8Pb6|D6>f!E=XVygP)sPiG+$%UQRx$a}5 zomZPxwb9@d!51eMkG^2NnbB;MX+ zke9bUb?{RC$v@B)zH=TzC&1Uoh(_GtLD+cvgQJ-;{Y!3>ZCno6Fxx^~;NrRN_Q&zP za0J;i#ou}YZ}P)8^#P@_EPw(@M-SI(MYoFg`NiH}j|G6)nH>KK9jU}Nx;|3DDtLg>%ez`fz<`q% zP$0P;IL`^mjrI?decVghkHgvFV*xnKb-_C8N{9epFM(cb^F^BH>u1AzDuDU>XG=IQ z+s<02eEMuNw46>Hr1?QHbW9!Rwpsb{uKL)zL(2}fU=@1iWLB-;8 zZE(Uq57_+7wcTy=@e35L&E78`_#Did7FBSzeT6!3NEr#9deahCG1)a`R{$wK+-d!f zGSw!2>C-zqaAmJ{n@`eNddtUhfn>S5Qr4;1xiWTAwr2)Ab@sJipjkfNLB7sa3BbrS zQ++q2f4-?7oP`$d(5idivgTA1R{4c*-v>BloT#t9Xt0ldeZKwO-J^*u4qCbJ;BF0_ zAP*8|b}F*ko`;D+PYvC@DA&1rX6tr=WA{ zvm;~xdj|R!{!y*LPqL#ZXdatLc& zf4U3IKBvxu;!EY>igzS5ti};c^qmyJW@AynW9IRQGyDEX-iTNGEP;6g!eBmxeWGTS zg9~zA)mzqgcRP&Xr~!lh(^!>RSCI`Eo@#!N&cXrZKvOxa7VB!)q`sGrX0Ro8jwtiQ z1}LBFw7*WB<^ns42AoD=UV}XQZY5_Be;k9$Rdm>CuzInYA(W&nWTJhWtKc2T2Xs4u z4H;{5@9jYqz$gM}$9UjdI{}&_ts_{Oxj4w^zW z$AZ*6P}p*)lGH=bC-#|&hMbq8e-i`;TQElIlqK6ZS7hz%bH>$^VLt%?&e;j4|nhJW` z){^#Y(yENMmgo+d`cpmTvjND{I2~plkYp3taU9z}WjhpjJYT!UUh1|whg$f+K$WV# z(c{LHs044|)KP#s3v6hxjIxO8y1(n}TupTnfZTa@5*}3Pw5n|_Sl4zcGZm$O7wVyU zlmr}etU|x8u^J(zofMpje+K#c;c@5>Czw&ISS6bliUSI00Mn&BQgv9_rkw=h09h6R zQdO;~g+g@yq!^YMgA72-fYh)8heH`W24JlB8A`C7+mzJ5GRB$1%2^mvG!L)=?P#vK zH0Z!M!E1BM#?R}9eAgIAAw{bN%CZy$VBiU01*<6oW)8!j6DGPYf7J6%V;z~=N|@pq zjTF?I%$W#qMLPi>Lz^x1s_ihUZ+yqlZe{y~Hu!oiY4`%oMHnY|-OJczRa}W`lB-&Z zsXVYn0!LN0DMsAeJ8X&lLk*bq_&sl@vJS?=1C(Yz)t(V1R!Wd<@!By>+bCzGes_3R z0%Bt|lMCy?F(z*-f7@h^5W{{4E{)E2kMzA$Z@>iu2i?r>SbCGnN2)^Yq_Tl|X5cY_ z=|dK=h3?-FGZ1x29!At z+u!AprK1l~P+g4$)#FQhJ0bMJm{p)KIM_OG@5q}6X**;bGAMLu6#I_+r5zjl6-))t zP%H>#D})o2fNEsm1uz@vJ+C?szs44->PF_4J}}-3s9L%e?JM|nBwQC9&)XQBLT*~> zUY9gAv#f|Pf2$nL6fMr5#|d6<2{-ux8!=q4fEYqy^F8KWlX|MkMgUvBK)}GEszVYNvENMVYkf&CJ%sQL`RZVQ!IslkC4~PLXe`So6Z=rbts-zTu<#w;KxGbo0 zWTZbX&n1@}hp`WFfLCiNW3ZD^a|kUIvIxS5r|83$4qV<{ZFwN@de8BEt%K{7LJa@X z4^zk@)Vq3*D(e6yWU4IZyb9a0!RmTB##?6R!hYDURb9#y*2pyy3PHwHUT>DiU_s|( zLjIa%e-wI;E|lt)q5xvYLxxiBuC_ez*Tu}f<(M)_-YV*NiXyv;08I3_6r6=F)HH9a zs*JL|-un`6xQZvY6)3de7*qU~ha)A`?4E*KKrKt*!C2NjwW1eeHOQxEWGp5ZYkGFR zvMP?LwtRX#>^L46yxt0m-P;78%Tn=vhmbi2e=^{_eSMXND1FQ3o*u#OhsJP`7_#>zclkB7?G6W5Bv8Rl~ThdE8JS$i1=b7f2SetihUn ze`WRD)J-0U^)$8V3l6@BLG`Hy@02Jy7Ztpivu`Zm?QDY<4Dh|oqG=0J1e{h1Q9^8o zdB9wY|D;F#<=7~z8Fy|a=A{*-aKpT9J$Q!NKC@W@clk_sJ%3kM7E2ud+%`}!77ZNM zOJTlns=ot3;i&&J!v&1aSi$3w?XvCre-56k=dl^9Vz@$$db}sjLcQ=5Z1g_=&}QCj zlRG|KrcYjd$kPtlLc4wwem?YJ(29mwpEY|)G4pT~Gm-Fv6a8I!DH!j zzRMtVcV!eAn3%qrf3m|^ zMqjiQPwV43E$|58yq=PuU`R^qk9Oc$Uu_xRZ+Vz~jT3GaYtF*ky*xFGvuDOke?4w) zeR{+fR)ou#t7o)FkXUBv+;B0KWdlaZ^9@noOAOp?ioc8n&oS^I2G+$d8~i`w&NXO0 zR2hChk#9%_+vRO)`X?dza(SA3e*wrRkHW&&t!+KiSQyoA;|s(#iVd}v;>qS2(CTxL zSF@Ad;x^%D2l#30voy<%UEzDrBHDxygBG_l$w2JFF+4NYUy*gsVYj-AYq^66HM_m? z+pttcfBAmxQeR78;cEdk?rum`cR7un!73a?zR#xNh2k9R?#H;r>k6Die<^<6gAkMb zOHkrXE;yVTEAvEzef>stgC9Q|r}m<|w**}zc(jt1?>*UC+OOSWfo||X%*A{(FnBN4 zu|0!dH1=(CPmxe$r|Pi1nd80F=>*R*JKBVH<0{+CuJva8(tdq=w&7aWAOPrW%$$9} zXu)UUVEOJx_6JVoJk4;q{N%XiQ1Ke4<^K11_Z>WAR7wYbc2NFv_#>VNMCH}4#HnN0ttKZga*Fax`- z?k&PDHLkV1h1F BdV&A| delta 4044 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 0 { - viewController.hidesBottomBarWhenPushed = true - if children.count > 1 { - viewController.hidesBottomBarWhenPushed = false - } - } - super.pushViewController(viewController, animated: true) + func setUpNavigation() { + if #available(iOS 13.0, *) { + let appearance = UINavigationBarAppearance() + appearance.backgroundImage = UIImage() + appearance.backgroundColor = .white + appearance.shadowColor = .white + navigationBar.standardAppearance = appearance + navigationBar.scrollEdgeAppearance = appearance } + } + override public func pushViewController(_ viewController: UIViewController, animated: Bool) { + if children.count > 0 { + viewController.hidesBottomBarWhenPushed = true + if children.count > 1 { + viewController.hidesBottomBarWhenPushed = false + } + } + super.pushViewController(viewController, animated: true) + } } diff --git a/app/NETabBarController.swift b/app/NETabBarController.swift index dfb683bf..d2bfd419 100644 --- a/app/NETabBarController.swift +++ b/app/NETabBarController.swift @@ -1,6 +1,7 @@ -// Copyright (c) 2022 NetEase, Inc. All rights reserved. -// Use of this source code is governed by a MIT license that can be found in the LICENSE file. +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. import UIKit import NIMSDK @@ -13,97 +14,109 @@ import NEKitChatUI import NEKitContactUI class NETabBarController: UITabBarController { + private var sessionUnreadCount = 0 + private var contactUnreadCount = 0 - private var sessionUnreadCount = 0 - private var contactUnreadCount = 0 - - - override func viewDidLoad() { - super.viewDidLoad() - setUpControllers() - setUpSessionBadgeValue() - setUpContactBadgeValue() - NIMSDK.shared().conversationManager.add(self) - NIMSDK.shared().systemNotificationManager.add(self) - } + override func viewDidLoad() { + super.viewDidLoad() + setUpControllers() + setUpSessionBadgeValue() + setUpContactBadgeValue() + NIMSDK.shared().conversationManager.add(self) + NIMSDK.shared().systemNotificationManager.add(self) + } - func setUpControllers(){ - //chat - let chat = ConversationController() - chat.view.backgroundColor = UIColor.init(hexString: "#e9eff5") - chat.tabBarItem = UITabBarItem(title: "消息", image: UIImage(named: "chat"), selectedImage: UIImage(named: "chatSelect")?.withRenderingMode(.alwaysOriginal)) - let chatNav = NENavigationController(rootViewController: chat) - - //qchat - let qchat = QChatHomeViewController() - qchat.view.backgroundColor = UIColor.init(hexString: "#e9eff5") - qchat.tabBarItem = UITabBarItem(title: "圈组", image: UIImage(named: "qchat_tabbar_icon"), selectedImage: UIImage(named: "qchat_tabbar_icon")?.withRenderingMode(.alwaysOriginal)) - let qChatNav = NENavigationController(rootViewController: qchat) - - - // Contacts - let contactVC = ContactsViewController() - contactVC.tabBarItem = UITabBarItem(title: "通讯录", image: UIImage(named: "contact"), selectedImage: UIImage(named: "contactSelect")?.withRenderingMode(.alwaysOriginal)) - contactVC.title = "通讯录" - let contactsNav = NENavigationController(rootViewController: contactVC) - - // Me - let meVC = MeViewController() - meVC.view.backgroundColor = UIColor.white - meVC.tabBarItem = UITabBarItem(title: "我", image: UIImage(named: "person"), selectedImage: UIImage(named: "personSelect")?.withRenderingMode(.alwaysOriginal)) - let meNav = NENavigationController(rootViewController: meVC) - - tabBar.backgroundColor = .white - self.viewControllers = [chatNav,qChatNav,contactsNav,meNav] - self.selectedIndex = 0 - } - - func setUpSessionBadgeValue(){ - sessionUnreadCount = ConversationRepo().getMsgUnreadCount(notify: true) - if sessionUnreadCount > 0 { - self.tabBar.showBadgOn(index: 0) - }else { - self.tabBar.hideBadg(on: 0) - } - } - - func setUpContactBadgeValue() { - contactUnreadCount = NIMSDK.shared().systemNotificationManager.allUnreadCount() - if contactUnreadCount > 0 { - self.tabBar.showBadgOn(index: 2) - }else { - self.tabBar.hideBadg(on: 2) - } - } - - private func refreshSessionBadge(){ - setUpSessionBadgeValue() + func setUpControllers() { + // chat + let chat = ConversationController() + chat.view.backgroundColor = UIColor(hexString: "#e9eff5") + chat.tabBarItem = UITabBarItem( + title: NSLocalizedString("message", comment: ""), + image: UIImage(named: "chat"), + selectedImage: UIImage(named: "chatSelect")?.withRenderingMode(.alwaysOriginal) + ) + let chatNav = NENavigationController(rootViewController: chat) + + // qchat + let qchat = QChatHomeViewController() + qchat.view.backgroundColor = UIColor(hexString: "#e9eff5") + qchat.tabBarItem = UITabBarItem( + title: NSLocalizedString("qchat", comment: ""), + image: UIImage(named: "qchat_tabbar_icon"), + selectedImage: UIImage(named: "qchat_tabbar_icon")?.withRenderingMode(.alwaysOriginal) + ) + let qChatNav = NENavigationController(rootViewController: qchat) + + // Contacts + let contactVC = ContactsViewController() + contactVC.tabBarItem = UITabBarItem( + title: NSLocalizedString("contact", comment: ""), + image: UIImage(named: "contact"), + selectedImage: UIImage(named: "contactSelect")?.withRenderingMode(.alwaysOriginal) + ) + contactVC.title = NSLocalizedString("contact", comment: "") + let contactsNav = NENavigationController(rootViewController: contactVC) + + // Me + let meVC = MeViewController() + meVC.view.backgroundColor = UIColor.white + meVC.tabBarItem = UITabBarItem( + title: NSLocalizedString("mine", comment: ""), + image: UIImage(named: "person"), + selectedImage: UIImage(named: "personSelect")?.withRenderingMode(.alwaysOriginal) + ) + let meNav = NENavigationController(rootViewController: meVC) + + tabBar.backgroundColor = .white + viewControllers = [chatNav, qChatNav, contactsNav, meNav] + selectedIndex = 0 + } + + func setUpSessionBadgeValue() { + sessionUnreadCount = ConversationRepo().getMsgUnreadCount(notify: true) + if sessionUnreadCount > 0 { + tabBar.showBadgOn(index: 0) + } else { + tabBar.hideBadg(on: 0) } - - deinit { - NIMSDK.shared().systemNotificationManager.remove(self) - NIMSDK.shared().conversationManager.remove(self) + } + + func setUpContactBadgeValue() { + contactUnreadCount = NIMSDK.shared().systemNotificationManager.allUnreadCount() + if contactUnreadCount > 0 { + tabBar.showBadgOn(index: 2) + } else { + tabBar.hideBadg(on: 2) } + } + + private func refreshSessionBadge() { + setUpSessionBadgeValue() + } + + deinit { + NIMSDK.shared().systemNotificationManager.remove(self) + NIMSDK.shared().conversationManager.remove(self) + } } -extension NETabBarController:NIMConversationManagerDelegate { - - func didAdd(_ recentSession: NIMRecentSession, totalUnreadCount: Int) { - refreshSessionBadge() - } - - func didUpdate(_ recentSession: NIMRecentSession, totalUnreadCount: Int) { - refreshSessionBadge() - } - - func didRemove(_ recentSession: NIMRecentSession, totalUnreadCount: Int) { - refreshSessionBadge() - } +extension NETabBarController: NIMConversationManagerDelegate { + func didAdd(_ recentSession: NIMRecentSession, totalUnreadCount: Int) { + refreshSessionBadge() + } + + func didUpdate(_ recentSession: NIMRecentSession, totalUnreadCount: Int) { + refreshSessionBadge() + } + + func didRemove(_ recentSession: NIMRecentSession, totalUnreadCount: Int) { + refreshSessionBadge() + } } -extension NETabBarController:NIMSystemNotificationManagerDelegate { - func onSystemNotificationCountChanged(_ unreadCount: Int) { - contactUnreadCount = unreadCount - setUpContactBadgeValue() - } +extension NETabBarController: NIMSystemNotificationManagerDelegate { + func onSystemNotificationCountChanged(_ unreadCount: Int) { + contactUnreadCount = unreadCount + setUpContactBadgeValue() + } } diff --git a/app/ViewController.swift b/app/ViewController.swift index 3e6a873d..18bc3f23 100644 --- a/app/ViewController.swift +++ b/app/ViewController.swift @@ -1,76 +1,98 @@ -// Copyright (c) 2022 NetEase, Inc. All rights reserved. -// Use of this source code is governed by a MIT license that can be found in the LICENSE file. +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. import UIKit +import NEKitCommonUI class ViewController: UIViewController { + lazy var launchIcon: UIImageView = { + let imageView = UIImageView() + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.image = UIImage(named: "launchIcon") + return imageView + }() - lazy var launchIcon : UIImageView = { - let imageView = UIImageView() - imageView.translatesAutoresizingMaskIntoConstraints = false - imageView.image = UIImage(named: "launch_icon") - return imageView - }() - - lazy var copyrightImage: UIImageView = { - let imageView = UIImageView() - imageView.translatesAutoresizingMaskIntoConstraints = false - imageView.image = UIImage(named: "copy_right") - return imageView - }() - - lazy var slogan: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.text = "真正稳定的IM云服务" - label.font = UIFont.systemFont(ofSize: 16.0) - label.textColor = UIColor(hexString: "666666") - return label - }() - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - setupUI() - } - - func setupUI(){ - - view.addSubview(launchIcon) - if #available(iOS 11.0, *) { - NSLayoutConstraint.activate([ - launchIcon.centerXAnchor.constraint(equalTo: view.centerXAnchor), - launchIcon.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 160.0) - ]) - }else { - NSLayoutConstraint.activate([ - launchIcon.centerXAnchor.constraint(equalTo: view.centerXAnchor), - launchIcon.topAnchor.constraint(equalTo: view.topAnchor, constant: 160.0) - ]) - } - - view.addSubview(slogan) - NSLayoutConstraint.activate([ - slogan.centerXAnchor.constraint(equalTo: view.centerXAnchor), - slogan.topAnchor.constraint(equalTo: launchIcon.bottomAnchor, constant: 12.0) - - ]) - - view.addSubview(copyrightImage) - if #available(iOS 11.0, *) { - NSLayoutConstraint.activate([ - copyrightImage.centerXAnchor.constraint(equalTo: view.centerXAnchor), - copyrightImage.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -48) - ]) - } else { - NSLayoutConstraint.activate([ - copyrightImage.centerXAnchor.constraint(equalTo: view.centerXAnchor), - copyrightImage.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -48) - ]) - } + lazy var launchIconLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.text = NSLocalizedString("appName", comment: "") + label.font = UIFont.systemFont(ofSize: 24.0) + label.textColor = UIColor(hexString: "333333") + return label + }() + + lazy var copyright: UIButton = { + let btn = UIButton() + btn.translatesAutoresizingMaskIntoConstraints = false + btn.setImage(UIImage(named: "yunxin_brand"), for: .normal) + btn.setTitle(NSLocalizedString("brand_des", comment: ""), for: .normal) + btn.titleLabel?.font = UIFont.systemFont(ofSize: 16.0) + btn.setTitleColor(UIColor(hexString: "333333"), for: .normal) + btn.layoutButtonImage(style: .left, space: 5.0) + return btn + }() + + lazy var slogan: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.text = NSLocalizedString("real_service", comment: "") + label.font = UIFont.systemFont(ofSize: 16.0) + label.textColor = UIColor(hexString: "666666") + return label + }() + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + setupUI() + } + + func setupUI() { + view.addSubview(launchIcon) + if #available(iOS 11.0, *) { + NSLayoutConstraint.activate([ + launchIcon.centerXAnchor.constraint(equalTo: view.centerXAnchor), + launchIcon.topAnchor.constraint( + equalTo: view.safeAreaLayoutGuide.topAnchor, + constant: 145.0 + ), + ]) + } else { + NSLayoutConstraint.activate([ + launchIcon.centerXAnchor.constraint(equalTo: view.centerXAnchor), + launchIcon.topAnchor.constraint(equalTo: view.topAnchor, constant: 145.0), + ]) } + view.addSubview(launchIconLabel) + NSLayoutConstraint.activate([ + launchIconLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), + launchIconLabel.topAnchor.constraint(equalTo: launchIcon.bottomAnchor, constant: -12.0), + ]) -} + view.addSubview(slogan) + NSLayoutConstraint.activate([ + slogan.centerXAnchor.constraint(equalTo: view.centerXAnchor), + slogan.topAnchor.constraint(equalTo: launchIconLabel.bottomAnchor, constant: 12.0), + + ]) + view.addSubview(copyright) + if #available(iOS 11.0, *) { + NSLayoutConstraint.activate([ + copyright.centerXAnchor.constraint(equalTo: view.centerXAnchor), + copyright.bottomAnchor.constraint( + equalTo: view.safeAreaLayoutGuide.bottomAnchor, + constant: -48 + ), + ]) + } else { + NSLayoutConstraint.activate([ + copyright.centerXAnchor.constraint(equalTo: view.centerXAnchor), + copyright.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -48), + ]) + } + } +} diff --git a/app/en.lproj/InfoPlist.strings b/app/en.lproj/InfoPlist.strings new file mode 100644 index 00000000..d7aa4665 --- /dev/null +++ b/app/en.lproj/InfoPlist.strings @@ -0,0 +1,10 @@ + +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +"NSCameraUsageDescription"="Can't take photo before agree"; +"NSPhotoLibraryAddUsageDescription"="Can't upload photo before agree"; +"NSPhotoLibraryUsageDescription"="Can't use album before agree"; +"NSMicrophoneUsageDescription"="Can't use microphone before agree"; +"CFBundleDisplayName"="CommsEase IM"; diff --git a/app/en.lproj/Localizable.strings b/app/en.lproj/Localizable.strings new file mode 100644 index 00000000..19bc34c6 --- /dev/null +++ b/app/en.lproj/Localizable.strings @@ -0,0 +1,58 @@ + +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +"open_push"="Please open the push in the setting"; +"message"="Message"; +"qchat"="Qchat"; +"contact"="Contact"; +"mine"="Me"; +"logout"="Log out"; +"about_yunxin"="About CommsEase"; +"want_to_logout"="Sure to log out?"; +"person_info"="My Proflie"; +"cancel"="Cancel"; +"setting_birthday_failure"="Set birthday Failed"; +"setting_head_failure"="Set Avatar Failed"; +"setting_nickname_failure"="Set Nick Name Failed"; +"male"="Male"; +"female"="Female"; +"change_gender_failure"="Set Sex Failed"; +"change_phone_failure"="Set mobile Failed"; +"change_email_failure"="Set e-mail Failed"; +"change_sign_failure"="Set What's up Failed"; +"copy_success"="Copy ssuccessfully"; +"save"="Save"; +"birehday"="Birthday"; +"phone"="Mobile"; +"email"="E-mail"; +"sign_remind"="sign remind"; + +"message_remind"="Message Notification"; +"product_intro"="Product introduction"; +"receiver_mode"="Handset mode"; +"delete_friend"="删除好友是否同步删除备注";//功能移除可删除 +"message_read_function"="Read/Unread"; +"version"="Version"; +"headImage"="Avator"; +"nickname"="Nick Name"; +"account"="Account"; +"gneder"="Sex"; +"unknown"="Unknown"; +"individuality_sign"="What's up"; +"new_message_remind"="message notification"; +"ring_mode"="Ring mode"; +"vibration_mode"="Vibration mode"; +"syn_receive_push"="PC/Web同步接收推送";//功能移除可删除 +"display_message_detail"="displays the details in notification bar"; +"setting"="Setting"; +"confirm"="yes"; +"cancel"="cancel"; +"real_service"="Truly Stable IM Product"; +"appName"="CommsEase IM"; +"brand_des"="CommsEase"; + +//image + + From 855ad90e848545a2c04bb9b2e68886df43da067c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=96=84=E6=A0=8B?= Date: Mon, 19 Sep 2022 16:29:37 +0800 Subject: [PATCH 5/6] =?UTF-8?q?appdelegate=20=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/AppDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/AppDelegate.swift b/app/AppDelegate.swift index dfb682fd..a4a4d760 100644 --- a/app/AppDelegate.swift +++ b/app/AppDelegate.swift @@ -54,7 +54,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD center.requestAuthorization(options: [.badge, .sound, .alert]) { grant, error in if grant == false { DispatchQueue.main.async { - UIApplication.shared.keyWindow?.makeToast("请到设置中开启推送功能") + UIApplication.shared.keyWindow?.makeToast(NSLocalizedString("open_push", comment: "")) } } } From 84057df514754daad14dd709aef1001381f3c607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=96=84=E6=A0=8B?= Date: Mon, 19 Sep 2022 18:42:16 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=A3=B3=E5=AD=90?= =?UTF-8?q?=E5=B7=A5=E7=A8=8B=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.xcodeproj/project.pbxproj | 16 +++++++- app/Info.plist | 6 ++- app/zh-Hans.lproj/InfoPlist.strings | 10 +++++ app/zh-Hans.lproj/Localizable.strings | 56 +++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 app/zh-Hans.lproj/InfoPlist.strings create mode 100644 app/zh-Hans.lproj/Localizable.strings diff --git a/app.xcodeproj/project.pbxproj b/app.xcodeproj/project.pbxproj index ec3d51ba..38ea292f 100644 --- a/app.xcodeproj/project.pbxproj +++ b/app.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 393759E928123E5E004F0AEC /* MessageRemindViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393759E828123E5E004F0AEC /* MessageRemindViewModel.swift */; }; 393759EB2812951E004F0AEC /* InputPersonInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393759EA2812951E004F0AEC /* InputPersonInfoController.swift */; }; 39A36F8128002CFF009B07A9 /* NETabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A36F8028002CFF009B07A9 /* NETabBarController.swift */; }; + 39E9E27728D87E9800A11820 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 39E9E27528D87E9800A11820 /* Localizable.strings */; }; 39F793C827E20434007F63FF /* NENavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39F793C727E20434007F63FF /* NENavigationController.swift */; }; 4B3B9BDF277AFEE50091A74E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3B9BDE277AFEE50091A74E /* AppDelegate.swift */; }; 4B3B9BE3277AFEE50091A74E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3B9BE2277AFEE50091A74E /* ViewController.swift */; }; @@ -63,6 +64,8 @@ 393759E828123E5E004F0AEC /* MessageRemindViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRemindViewModel.swift; sourceTree = ""; }; 393759EA2812951E004F0AEC /* InputPersonInfoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputPersonInfoController.swift; sourceTree = ""; }; 39A36F8028002CFF009B07A9 /* NETabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NETabBarController.swift; sourceTree = ""; }; + 39E9E27628D87E9800A11820 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 39E9E27828D87EA000A11820 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 39F793C727E20434007F63FF /* NENavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NENavigationController.swift; sourceTree = ""; }; 4B3B9BDB277AFEE50091A74E /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4B3B9BDE277AFEE50091A74E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -70,7 +73,6 @@ 4B3B9BE5277AFEE50091A74E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 4B3B9BE7277AFEE70091A74E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 4B3B9BEA277AFEE70091A74E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 4B3B9BEC277AFEE70091A74E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9E98CA4B27BCEC15008A8738 /* MeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeViewController.swift; sourceTree = ""; }; 9EA2014027BE4B4800F8BBD0 /* AppKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppKey.swift; sourceTree = ""; }; A9E4316D70F3EF2E6BD5E5CB /* Pods-app.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.release.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.release.xcconfig"; sourceTree = ""; }; @@ -148,11 +150,11 @@ 4B3B9BDE277AFEE50091A74E /* AppDelegate.swift */, 9EA2014027BE4B4800F8BBD0 /* AppKey.swift */, 4B3B9BE2277AFEE50091A74E /* ViewController.swift */, + 39E9E27528D87E9800A11820 /* Localizable.strings */, 393759D1281033C9004F0AEC /* Mine */, 4B3B9BE4277AFEE50091A74E /* Main.storyboard */, 4B3B9BE7277AFEE70091A74E /* Assets.xcassets */, 4B3B9BE9277AFEE70091A74E /* LaunchScreen.storyboard */, - 4B3B9BEC277AFEE70091A74E /* Info.plist */, ); path = app; sourceTree = ""; @@ -230,6 +232,7 @@ buildActionMask = 2147483647; files = ( 4B3B9BEB277AFEE70091A74E /* LaunchScreen.storyboard in Resources */, + 39E9E27728D87E9800A11820 /* Localizable.strings in Resources */, 4B3B9BE8277AFEE70091A74E /* Assets.xcassets in Resources */, 4B3B9BE6277AFEE50091A74E /* Main.storyboard in Resources */, ); @@ -309,6 +312,15 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ + 39E9E27528D87E9800A11820 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 39E9E27628D87E9800A11820 /* en */, + 39E9E27828D87EA000A11820 /* zh-Hans */, + ); + name = Localizable.strings; + sourceTree = ""; + }; 4B3B9BE4277AFEE50091A74E /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( diff --git a/app/Info.plist b/app/Info.plist index ee29e9a1..a856f55d 100644 --- a/app/Info.plist +++ b/app/Info.plist @@ -17,7 +17,11 @@ NSCameraUsageDescription 若不允许,你将无法拍摄照片 NSPhotoLibraryUsageDescription - 若不允许,你将无法导入照片上传头像。 + 若不允许,你将无法导入照片 + NSPhotoLibraryAddUsageDescription + 若不允许,则无法使用相册功能 + NSMicrophoneUsageDescription + 若不允许,则无法使用麦克风功能 UIApplicationSupportsMultipleScenes diff --git a/app/zh-Hans.lproj/InfoPlist.strings b/app/zh-Hans.lproj/InfoPlist.strings new file mode 100644 index 00000000..262ad21e --- /dev/null +++ b/app/zh-Hans.lproj/InfoPlist.strings @@ -0,0 +1,10 @@ + +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +"NSCameraUsageDescription"="若不允许,你将无法拍摄照片"; +"NSPhotoLibraryAddUsageDescription"="若不允许,你将无法导入照片"; +"NSPhotoLibraryUsageDescription"="若不允许,则无法使用相册功能"; +"NSMicrophoneUsageDescription"="若不允许,则无法使用麦克风功能"; +"CFBundleDisplayName"="云信IM"; diff --git a/app/zh-Hans.lproj/Localizable.strings b/app/zh-Hans.lproj/Localizable.strings new file mode 100644 index 00000000..f9ea31ba --- /dev/null +++ b/app/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,56 @@ + +// Copyright (c) 2022 NetEase, Inc. All rights reserved. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +"open_push"="请到设置中开启推送功能"; +"message"="消息"; +"qchat"="圈组"; +"contact"="通讯录"; +"mine"="我"; +"logout"="退出登录"; +"about_yunxin"="关于云信"; +"want_to_logout"="确认要退出登录吗?"; +"person_info"="个人信息"; +"cancel"="取消"; +"setting_birthday_failure"="设置生日失败"; +"setting_head_failure"="修改头像失败"; +"setting_nickname_failure"="修改昵称失败"; +"male"="男"; +"female"="女"; +"change_gender_failure"="修改性别失败"; +"change_phone_failure"="修改手机号失败"; +"change_email_failure"="修改邮箱失败"; +"change_sign_failure"="修改签名失败"; +"copy_success"="复制成功"; +"save"="保存"; +"birehday"="生日"; +"phone"="手机"; +"email"="邮箱"; +"sign_remind"="个性签名消息提醒"; + +"message_remind"="消息提醒"; +"product_intro"="产品介绍"; +"receiver_mode"="听筒模式"; +"delete_friend"="删除好友是否同步删除备注"; +"message_read_function"="消息已读未读功能"; +"version"="版本号"; +"headImage"="头像"; +"nickname"="昵称"; +"account"="账号"; +"gender"="性别"; +"unknown"="未知"; +"individuality_sign"="个性签名"; +"new_message_remind"="新消息通知"; +"ring_mode"="响铃模式"; +"vibration_mode"="震动模式"; +"syn_receive_push"="PC/Web同步接收推送"; +"display_message_detail"="通知栏显示消息详情"; +"setting"="设置"; +"confirm"="确认"; +"cancel"="取消"; +"real_service"="真正稳定的IM云服务"; +"appName"="云信IM"; +"brand_des"="网易云信"; +//image +