- 通用弹窗
- 解散圈子弹窗 - 退出登录、重新登录 config接口、IM登录 调用调整 - 个人中心 接口接入 - 账号与安全
|
|
@ -196,6 +196,13 @@
|
|||
30EFF3A82FD7C6A400EB35D4 /* GroupSettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3A72FD7C6A400EB35D4 /* GroupSettingViewModel.swift */; };
|
||||
30EFF3AE2FD7FF1400EB35D4 /* TextInputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3AC2FD7FF1400EB35D4 /* TextInputViewController.swift */; };
|
||||
30EFF3B02FD8122E00EB35D4 /* GroupTagListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3AF2FD8122E00EB35D4 /* GroupTagListView.swift */; };
|
||||
30EFF3B32FD8F1C200EB35D4 /* ReviewMemberListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3B22FD8F1C200EB35D4 /* ReviewMemberListView.swift */; };
|
||||
30EFF3B52FD8F1D000EB35D4 /* ReviewMemberListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3B42FD8F1D000EB35D4 /* ReviewMemberListVC.swift */; };
|
||||
30EFF3B72FD8F86200EB35D4 /* ReviewMemberListVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3B62FD8F86200EB35D4 /* ReviewMemberListVM.swift */; };
|
||||
30EFF3B92FD8FC5200EB35D4 /* VerificationPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3B82FD8FC5200EB35D4 /* VerificationPopView.swift */; };
|
||||
30EFF3BB2FD90D7600EB35D4 /* ConfirmPopVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3BA2FD90D7600EB35D4 /* ConfirmPopVC.swift */; };
|
||||
30EFF3BE2FD958A100EB35D4 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3BD2FD958A100EB35D4 /* AccountView.swift */; };
|
||||
30EFF3C02FD958AE00EB35D4 /* AccountVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3BF2FD958AE00EB35D4 /* AccountVC.swift */; };
|
||||
C49B37352A45A02C28FF41BA /* Pods_QuickLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1C77B42994F352054070537 /* Pods_QuickLocation.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
|
@ -399,6 +406,13 @@
|
|||
30EFF3A72FD7C6A400EB35D4 /* GroupSettingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupSettingViewModel.swift; sourceTree = "<group>"; };
|
||||
30EFF3AC2FD7FF1400EB35D4 /* TextInputViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInputViewController.swift; sourceTree = "<group>"; };
|
||||
30EFF3AF2FD8122E00EB35D4 /* GroupTagListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupTagListView.swift; sourceTree = "<group>"; };
|
||||
30EFF3B22FD8F1C200EB35D4 /* ReviewMemberListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewMemberListView.swift; sourceTree = "<group>"; };
|
||||
30EFF3B42FD8F1D000EB35D4 /* ReviewMemberListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewMemberListVC.swift; sourceTree = "<group>"; };
|
||||
30EFF3B62FD8F86200EB35D4 /* ReviewMemberListVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewMemberListVM.swift; sourceTree = "<group>"; };
|
||||
30EFF3B82FD8FC5200EB35D4 /* VerificationPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPopView.swift; sourceTree = "<group>"; };
|
||||
30EFF3BA2FD90D7600EB35D4 /* ConfirmPopVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmPopVC.swift; sourceTree = "<group>"; };
|
||||
30EFF3BD2FD958A100EB35D4 /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = "<group>"; };
|
||||
30EFF3BF2FD958AE00EB35D4 /* AccountVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountVC.swift; sourceTree = "<group>"; };
|
||||
3E4359082FC48D26003470A5 /* QuickLocation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuickLocation.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
93647DF3683AA5E71EC2FB1A /* Pods-QuickLocation.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QuickLocation.release.xcconfig"; path = "Target Support Files/Pods-QuickLocation/Pods-QuickLocation.release.xcconfig"; sourceTree = "<group>"; };
|
||||
D1C77B42994F352054070537 /* Pods_QuickLocation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_QuickLocation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
|
@ -768,8 +782,10 @@
|
|||
305A76242FCA8C7000227D26 /* GroupViewController.swift */,
|
||||
305A76252FCA8C7000227D26 /* GroupViewModel.swift */,
|
||||
305A76232FCA8C7000227D26 /* GroupView.swift */,
|
||||
30EFF3B82FD8FC5200EB35D4 /* VerificationPopView.swift */,
|
||||
307073E42FD18A20004C37CC /* GroupChat */,
|
||||
30EFF3A22FD7C58400EB35D4 /* GroupSetting */,
|
||||
30EFF3B12FD8F19E00EB35D4 /* ReviewMemberList */,
|
||||
3062E8B82FCEAC5600CEF511 /* CreateGroup */,
|
||||
30BAB8612FCD714700C33B5C /* Join */,
|
||||
30BAB84B2FCD2FA400C33B5C /* InviteJoin */,
|
||||
|
|
@ -821,9 +837,10 @@
|
|||
305A76392FCA8C7000227D26 /* Mine */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
305A76362FCA8C7000227D26 /* MineView.swift */,
|
||||
305A76372FCA8C7000227D26 /* MineViewController.swift */,
|
||||
305A76382FCA8C7000227D26 /* MineViewModel.swift */,
|
||||
305A76362FCA8C7000227D26 /* MineView.swift */,
|
||||
30EFF3BC2FD9585200EB35D4 /* Account */,
|
||||
);
|
||||
path = Mine;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -947,6 +964,7 @@
|
|||
305A76692FCA8C7000227D26 /* Pop */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30EFF3BA2FD90D7600EB35D4 /* ConfirmPopVC.swift */,
|
||||
305A76642FCA8C7000227D26 /* DLAlertPopVC.swift */,
|
||||
305A76652FCA8C7000227D26 /* DLCustomPopVC.swift */,
|
||||
305A76662FCA8C7000227D26 /* DLSheetPopVC.swift */,
|
||||
|
|
@ -1110,6 +1128,25 @@
|
|||
path = TextInput;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
30EFF3B12FD8F19E00EB35D4 /* ReviewMemberList */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30EFF3B42FD8F1D000EB35D4 /* ReviewMemberListVC.swift */,
|
||||
30EFF3B22FD8F1C200EB35D4 /* ReviewMemberListView.swift */,
|
||||
30EFF3B62FD8F86200EB35D4 /* ReviewMemberListVM.swift */,
|
||||
);
|
||||
path = ReviewMemberList;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
30EFF3BC2FD9585200EB35D4 /* Account */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30EFF3BF2FD958AE00EB35D4 /* AccountVC.swift */,
|
||||
30EFF3BD2FD958A100EB35D4 /* AccountView.swift */,
|
||||
);
|
||||
path = Account;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3E4358FF2FC48D26003470A5 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -1348,11 +1385,13 @@
|
|||
305A76B72FCA8C7000227D26 /* UIView+Extension.swift in Sources */,
|
||||
305A76B82FCA8C7000227D26 /* UIViewController+Extension.swift in Sources */,
|
||||
305A76B92FCA8C7000227D26 /* URL+Extension.swift in Sources */,
|
||||
30EFF3BE2FD958A100EB35D4 /* AccountView.swift in Sources */,
|
||||
305A76BA2FCA8C7000227D26 /* Wrapper.swift in Sources */,
|
||||
305A76BB2FCA8C7000227D26 /* BaseModelNew.swift in Sources */,
|
||||
305A76BC2FCA8C7000227D26 /* ListModel.swift in Sources */,
|
||||
305A76BD2FCA8C7000227D26 /* PaginationModel.swift in Sources */,
|
||||
305A76BE2FCA8C7000227D26 /* ResponseModel.swift in Sources */,
|
||||
30EFF3C02FD958AE00EB35D4 /* AccountVC.swift in Sources */,
|
||||
305A76BF2FCA8C7000227D26 /* ListService.swift in Sources */,
|
||||
305A76C02FCA8C7000227D26 /* BaseNavigationController.swift in Sources */,
|
||||
305A76C12FCA8C7000227D26 /* BaseViewController.swift in Sources */,
|
||||
|
|
@ -1396,8 +1435,10 @@
|
|||
305A76DF2FCA8C7000227D26 /* Single+ObjectMapper.swift in Sources */,
|
||||
30DC18602FD12A020041DCD1 /* VipWaivePopView.swift in Sources */,
|
||||
305A76E02FCA8C7000227D26 /* GroupView.swift in Sources */,
|
||||
30EFF3BB2FD90D7600EB35D4 /* ConfirmPopVC.swift in Sources */,
|
||||
30BAB8512FCD331C00C33B5C /* GroupAPI.swift in Sources */,
|
||||
305A76E12FCA8C7000227D26 /* GroupViewController.swift in Sources */,
|
||||
30EFF3B52FD8F1D000EB35D4 /* ReviewMemberListVC.swift in Sources */,
|
||||
30BAB84D2FCD2FDE00C33B5C /* InviteJoinView.swift in Sources */,
|
||||
305A76E22FCA8C7000227D26 /* GroupViewModel.swift in Sources */,
|
||||
305A76E32FCA8C7000227D26 /* GroupMemberView.swift in Sources */,
|
||||
|
|
@ -1408,6 +1449,7 @@
|
|||
3062E8B52FCE6BBA00CEF511 /* ScanVC.swift in Sources */,
|
||||
305A76E72FCA8C7000227D26 /* LoginView.swift in Sources */,
|
||||
305A76E82FCA8C7000227D26 /* LoginViewController.swift in Sources */,
|
||||
30EFF3B92FD8FC5200EB35D4 /* VerificationPopView.swift in Sources */,
|
||||
305A76E92FCA8C7000227D26 /* LoginViewModel.swift in Sources */,
|
||||
3062E8BC2FCEAC7100CEF511 /* CreateGroupVC.swift in Sources */,
|
||||
30BAB8632FCD716C00C33B5C /* JoinGroupVC.swift in Sources */,
|
||||
|
|
@ -1432,6 +1474,7 @@
|
|||
305A76F72FCA8C7000227D26 /* RouterTarget.swift in Sources */,
|
||||
307073E52FD18A20004C37CC /* GroupChatView.swift in Sources */,
|
||||
307073E62FD18A20004C37CC /* GroupChatVC.swift in Sources */,
|
||||
30EFF3B32FD8F1C200EB35D4 /* ReviewMemberListView.swift in Sources */,
|
||||
305A76F82FCA8C7000227D26 /* DLAlert.swift in Sources */,
|
||||
305A76F92FCA8C7000227D26 /* DLToast.swift in Sources */,
|
||||
305A76FA2FCA8C7000227D26 /* DLEmptyDataSet.swift in Sources */,
|
||||
|
|
@ -1461,6 +1504,7 @@
|
|||
305A770F2FCA8C7000227D26 /* DLCustomPopVC.swift in Sources */,
|
||||
30EFF29B2FD668C900EB35D4 /* VoiceRecordView.swift in Sources */,
|
||||
305A77102FCA8C7000227D26 /* DLSheetPopVC.swift in Sources */,
|
||||
30EFF3B72FD8F86200EB35D4 /* ReviewMemberListVM.swift in Sources */,
|
||||
305A77112FCA8C7000227D26 /* DLViewTransition.m in Sources */,
|
||||
3062E8BA2FCEAC6500CEF511 /* CreateGroupView.swift in Sources */,
|
||||
305A77192FCA8C7000227D26 /* CollectionHFlowLayout.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -114,6 +114,8 @@ public extension MoyaError {
|
|||
enum GatewayStatusCode: Int {
|
||||
// 请求成功
|
||||
case success = 0
|
||||
//
|
||||
case failure = -1
|
||||
// 版本过低
|
||||
case outdateVersion = 3
|
||||
/** ============== 风控 ============== */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Rectangle 168@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Rectangle 168@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
QuickLocation/Assets.xcassets/Common/button_bg_2.imageset/Rectangle 168@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
QuickLocation/Assets.xcassets/Common/button_bg_2.imageset/Rectangle 168@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group_1672@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group_1672@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "correct@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "correct@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "error@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "error@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "header_bg@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "header_bg@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
|
@ -24,6 +24,8 @@ extension DefaultsKeys {
|
|||
|
||||
/// 通知常量
|
||||
extension Notification.Name {
|
||||
/// 刷新用户config
|
||||
static let RefreshUserConfigNotification = Notification.Name("RefreshUserConfigNotification")
|
||||
/// 刷新用户圈子数据
|
||||
static let RefreshGroupInfoNotification = Notification.Name("RefreshGroupInfoNotification")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -337,10 +337,10 @@ extension UIView {
|
|||
|
||||
// MARK: - 时间戳
|
||||
extension UIView {
|
||||
func getDateInterval2String(date: String) -> String {
|
||||
func getDateInterval2String(date: String, dateFormat: String="yyyy.MM.dd HH:mm:ss") -> String {
|
||||
let date = Date(timeIntervalSince1970: TimeInterval(date.double))
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy.MM.dd HH:mm:ss"
|
||||
dateFormatter.dateFormat = dateFormat
|
||||
let dateString = dateFormatter.string(from: date)
|
||||
return dateString
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ struct UserConfigModel: Mappable {
|
|||
|
||||
/// 会员 1:非会员 2:普通会员 3:终身会员
|
||||
var vip: Int = 1
|
||||
/// 会员类型
|
||||
var vip_name: String = ""
|
||||
/// 会员过期时间
|
||||
var vip_expire_time: String = ""
|
||||
|
||||
/// Config
|
||||
var config: SystemConfigModel?
|
||||
|
|
@ -45,6 +49,8 @@ struct UserConfigModel: Mappable {
|
|||
head_pic <- (map["head_pic"], kIntTransformStr)
|
||||
config <- map["config"]
|
||||
vip <- map["vip"]
|
||||
vip_name <- map["vip_name"]
|
||||
vip_expire_time <- map["vip_expire_time"]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ extension ApiManager {
|
|||
// handleOutdateVersion(message)
|
||||
case GatewayStatusCode.userLoginExpair.rawValue: // token失效
|
||||
handleTokenExpired(msg: message)
|
||||
case GatewayStatusCode.noAuthority.rawValue: // 退出当前界面
|
||||
case GatewayStatusCode.failure.rawValue, GatewayStatusCode.noAuthority.rawValue: // 退出当前界面
|
||||
handlePopView(message)
|
||||
default:
|
||||
/// 统一提示错误
|
||||
|
|
@ -138,9 +138,9 @@ extension ApiManager {
|
|||
private func handleTokenExpired(msg: String?) {
|
||||
MainAsync {
|
||||
AppContextManager.shared.deleteAccount()
|
||||
// IMServiceManager.shared.logout()
|
||||
GroupIMService.shared.logout()
|
||||
DLToast.showError(text: msg ?? "登录失效,请重新登录!") {
|
||||
// AppDelegate.shared.showMainViewController()
|
||||
AppDelegate.shared.showMainViewController()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ enum Route: String {
|
|||
case groupChat = "groupChat"
|
||||
/// 圈子设置
|
||||
case groupSetting = "groupSetting"
|
||||
/// 账号与安全
|
||||
case account = "account"
|
||||
}
|
||||
|
||||
extension Route: RouterTarget {
|
||||
|
|
@ -80,8 +82,8 @@ extension AppRouter: AppRouterProtocol {
|
|||
return false
|
||||
}
|
||||
|
||||
if vc.isNeedLogin && !AppContextManager.shared.isGuest {
|
||||
// AppRouter.push(Route.login)
|
||||
if vc.isNeedLogin && AppContextManager.shared.isGuest {
|
||||
AppRouter.push(Route.login)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -122,12 +124,16 @@ extension AppRouter: AppRouterProtocol {
|
|||
|
||||
// MARK: - 加入圈子
|
||||
AppRouter.register(Route.joinGroup) { url, parameters in
|
||||
JoinGroupVC()
|
||||
let vc = JoinGroupVC()
|
||||
vc.isNeedLogin = true
|
||||
return vc
|
||||
}
|
||||
|
||||
// MARK: - 创建圈子
|
||||
AppRouter.register(Route.createGroup) { url, parameters in
|
||||
CreateGroupVC()
|
||||
let vc = CreateGroupVC()
|
||||
vc.isNeedLogin = true
|
||||
return vc
|
||||
}
|
||||
|
||||
// MARK: - 扫一扫
|
||||
|
|
@ -162,6 +168,13 @@ extension AppRouter: AppRouterProtocol {
|
|||
let groupId = parameters["groupId"].safeString
|
||||
return GroupSettingVC(groupId: groupId)
|
||||
}
|
||||
|
||||
// MARK: - 账号与安全
|
||||
AppRouter.register(Route.account) { url, parameters in
|
||||
let vc = AccountVC()
|
||||
vc.isNeedLogin = true
|
||||
return vc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,13 +47,14 @@ final class GroupChatVC: BaseViewController {
|
|||
setupMessageListener()
|
||||
setupVoiceRecording()
|
||||
setupPanelDismiss()
|
||||
setupKeyboard()
|
||||
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
IQKeyboardManager.shared.isEnabled = false
|
||||
IQKeyboardManager.shared.resignOnTouchOutside = false
|
||||
setupKeyboard()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
|
|
@ -69,6 +70,8 @@ final class GroupChatVC: BaseViewController {
|
|||
VoicePlayerManager.shared.stop()
|
||||
IQKeyboardManager.shared.isEnabled = true
|
||||
IQKeyboardManager.shared.resignOnTouchOutside = true
|
||||
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
// MARK: - Keyboard
|
||||
|
|
@ -418,6 +421,7 @@ final class GroupChatVC: BaseViewController {
|
|||
self.viewModel.memberList = response.list
|
||||
self.rootView.groupNameLabel.text = model.name
|
||||
self.rootView.groupAvatarView.image = model.groupIcon
|
||||
self.rootView.reviewBtn.isHidden = !model.is_owner
|
||||
self.viewModel.loadMessages()
|
||||
}.disposed(by: disposeBag)
|
||||
}
|
||||
|
|
@ -464,9 +468,9 @@ final class GroupChatVC: BaseViewController {
|
|||
self?.showBigImage(imgUrlList: [msg.imageUrl], currentPage: 0, projectiveView: cell.photoView)
|
||||
}
|
||||
return cell
|
||||
case let .notification(text):
|
||||
case let .notification(text, showTime, timestamp):
|
||||
let cell: NotificationMsgCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
cell.configure(text)
|
||||
cell.configure(text, showTime: showTime, timestamp: timestamp)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -628,6 +628,32 @@ class TextReceivedMsgCell: UITableViewCell {
|
|||
// MARK: - 通知消息cell
|
||||
final class NotificationMsgCell: UITableViewCell {
|
||||
|
||||
func configure(_ text: NSAttributedString, showTime: Bool, timestamp: TimeInterval) {
|
||||
timeLabel.isHidden = !showTime
|
||||
timeLabel.text = showTime ? formatTime(timestamp) : nil
|
||||
contentLabel.attributedText = text
|
||||
}
|
||||
|
||||
private func formatTime(_ t: TimeInterval) -> String {
|
||||
let date = Date(timeIntervalSince1970: t)
|
||||
let now = Date()
|
||||
let calendar = Calendar.current
|
||||
let f = DateFormatter()
|
||||
if calendar.isDateInToday(date) { f.dateFormat = "HH:mm" }
|
||||
else if calendar.isDateInYesterday(date) { f.dateFormat = "'昨天' HH:mm" }
|
||||
else if calendar.isDate(date, equalTo: now, toGranularity: .year) { f.dateFormat = "M-d HH:mm" }
|
||||
else { f.dateFormat = "yyyy-M-d HH:mm" }
|
||||
return f.string(from: date)
|
||||
}
|
||||
|
||||
private let timeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 12)
|
||||
label.textColor = UIColor(hexStr: "#999999")
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private let contentLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 12)
|
||||
|
|
@ -641,18 +667,23 @@ final class NotificationMsgCell: UITableViewCell {
|
|||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
|
||||
contentView.addSubview(timeLabel)
|
||||
contentView.addSubview(contentLabel)
|
||||
|
||||
timeLabel.layoutChain
|
||||
.top(10)
|
||||
.centerX()
|
||||
|
||||
contentLabel.layoutChain
|
||||
.edges(UIEdgeInsets(top: 10, left: 40, bottom: 10, right: 40))
|
||||
.topToBottomOfView(timeLabel, offset: 8)
|
||||
.edgesHorzontal(40)
|
||||
.bottom(10)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configure(_ text: String) {
|
||||
contentLabel.text = text
|
||||
}
|
||||
}
|
||||
// MARK: - 表情文件列表(共用)
|
||||
extension UITableViewCell {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ enum ChatSectionItem {
|
|||
case voiceReceived(ChatMessage)
|
||||
case imageSend(ChatMessage)
|
||||
case imageReceived(ChatMessage)
|
||||
case notification(String)
|
||||
case notification(NSAttributedString, showTime: Bool = false, timestamp: TimeInterval = 0)
|
||||
}
|
||||
|
||||
typealias ChatSectionModel = SectionModel<String, ChatSectionItem>
|
||||
|
|
@ -198,7 +198,7 @@ final class GroupChatViewModel {
|
|||
case let .send(m), let .received(m), let .emojiSend(m), let .emojiReceived(m),
|
||||
let .voiceSend(m), let .voiceReceived(m), let .imageSend(m), let .imageReceived(m):
|
||||
return m.timestamp
|
||||
case .notification: return 0
|
||||
case let .notification(_, _, ts): return ts
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +212,7 @@ final class GroupChatViewModel {
|
|||
case var .voiceReceived(m):m.showTime = show; return .voiceReceived(m)
|
||||
case var .imageSend(m): m.showTime = show; return .imageSend(m)
|
||||
case var .imageReceived(m):m.showTime = show; return .imageReceived(m)
|
||||
case .notification: return item
|
||||
case let .notification(text, _, ts): return .notification(text, showTime: show, timestamp: ts)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -278,10 +278,11 @@ final class GroupChatViewModel {
|
|||
|
||||
private func toSectionItem(_ msg: OIMMessageInfo) -> ChatSectionItem? {
|
||||
// 通知消息
|
||||
if msg.contentType.rawValue == 1501 || msg.contentType.rawValue == 1510,
|
||||
let noti = msg.notificationElem {
|
||||
let text = parseNotification(noti)
|
||||
if !text.isEmpty { return .notification(text) }
|
||||
if (msg.contentType.rawValue == 1501 || msg.contentType.rawValue == 1510 || msg.contentType.rawValue == 1520),
|
||||
let noti = msg.notificationElem,
|
||||
let text = parseNotification(noti, contentType: msg.contentType.rawValue) {
|
||||
let ts = TimeInterval(msg.sendTime) / 1000.0
|
||||
return .notification(text, showTime: false, timestamp: ts)
|
||||
}
|
||||
// 语音消息
|
||||
if msg.contentType.rawValue == 103 || msg.contentType.rawValue == 104 {
|
||||
|
|
@ -304,17 +305,38 @@ final class GroupChatViewModel {
|
|||
return chatMsg.isSelf ? .send(chatMsg) : .received(chatMsg)
|
||||
}
|
||||
|
||||
private func parseNotification(_ elem: OIMNotificationElem) -> String {
|
||||
private func parseNotification(_ elem: OIMNotificationElem, contentType: Int) -> NSAttributedString? {
|
||||
guard let data = elem.detail?.data(using: .utf8),
|
||||
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let group = json["group"] as? [String: Any],
|
||||
let _ = json["opUser"] as? [String: Any] else { return "" }
|
||||
let opUser = json["opUser"] as? [String: Any] else { return nil }
|
||||
|
||||
// 判断是否是当前用户:如果是自己创建的群聊显示"你创建了群聊",否则显示"XXX创建了群聊"
|
||||
let ownerID = group["ownerUserID"] as? String ?? ""
|
||||
let ownerNickName = getUserNickName(id: ownerID)
|
||||
let displayStr = ownerID == AppContextManager.shared.userId ? "圈子已经创建" : "\(ownerNickName) 创建了圈子"
|
||||
return displayStr
|
||||
switch contentType {
|
||||
case 1501, 1510:
|
||||
// 群创建通知
|
||||
let ownerID = group["ownerUserID"] as? String ?? ""
|
||||
let ownerNickName = getUserNickName(id: ownerID)
|
||||
let text = ownerID == AppContextManager.shared.userId ? "圈子已经创建" : "\(ownerNickName) 创建了圈子"
|
||||
return NSAttributedString(string: text)
|
||||
|
||||
case 1520:
|
||||
// 群名称改变通知
|
||||
let opUserID = opUser["userID"] as? String ?? ""
|
||||
let opNickName = getUserNickName(id: opUserID)
|
||||
let newName = group["groupName"] as? String ?? ""
|
||||
guard !newName.isEmpty else { return nil }
|
||||
|
||||
let tip = "\(opNickName) 将群名称修改为 "
|
||||
let result = NSMutableAttributedString(string: tip + newName)
|
||||
result.addAttribute(.font, value: UIFont.systemFont(ofSize: 12), range: NSRange(location: 0, length: result.length))
|
||||
// 群名用主题蓝色
|
||||
let nameRange = NSRange(location: tip.count, length: newName.utf16.count)
|
||||
result.addAttribute(.foregroundColor, value: UIColor(hexStr: "#16B3FF"), range: nameRange)
|
||||
return result
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,12 +12,18 @@ final class GroupIMService {
|
|||
|
||||
static let shared = GroupIMService()
|
||||
|
||||
private var isLogined = false
|
||||
private var isInited = false
|
||||
private var isLogining = false
|
||||
/// 登录进行中时排队的回调,登录结束统一回调,避免并发重复登录
|
||||
private var pendingLoginCompletions: [(Bool) -> Void] = []
|
||||
|
||||
private init() {}
|
||||
|
||||
// MARK: - Init SDK
|
||||
func initSDK() {
|
||||
// 幂等:SDK 全局只初始化一次
|
||||
guard !isInited else { return }
|
||||
isInited = true
|
||||
let config = OIMInitConfig()
|
||||
config.apiAddr = URLManager.shared.openIM_API
|
||||
config.wsAddr = URLManager.shared.openIM_WS
|
||||
|
|
@ -32,30 +38,64 @@ final class GroupIMService {
|
|||
}
|
||||
|
||||
// MARK: - Login
|
||||
func login(completion: @escaping (Bool) -> Void) {
|
||||
guard !isLogined, let token = AppContextManager.shared.imToken else {
|
||||
/// 确保已登录(幂等):已登录直接回调,否则发起登录;登录中则排队等待
|
||||
func ensureLogin(completion: @escaping (Bool) -> Void) {
|
||||
if OIMManager.manager.getLoginStatus() == .logged {
|
||||
completion(true)
|
||||
return
|
||||
}
|
||||
login(completion: completion)
|
||||
}
|
||||
|
||||
func login(completion: @escaping (Bool) -> Void) {
|
||||
// 已登录,直接成功
|
||||
if OIMManager.manager.getLoginStatus() == .logged {
|
||||
completion(true)
|
||||
return
|
||||
}
|
||||
guard let token = AppContextManager.shared.imToken, !token.isEmpty else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
let userId = AppContextManager.shared.userId
|
||||
guard !userId.isEmpty, !token.isEmpty else {
|
||||
guard !userId.isEmpty else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
// 登录中:排队,复用同一次登录结果,避免重复发起
|
||||
pendingLoginCompletions.append(completion)
|
||||
guard !isLogining else { return }
|
||||
isLogining = true
|
||||
|
||||
OIMManager.manager.login(userId, token: token) { [weak self] _ in
|
||||
self?.isLogined = true
|
||||
completion(true)
|
||||
} onFailure: { code, msg in
|
||||
self?.finishLogin(success: true)
|
||||
} onFailure: { [weak self] code, msg in
|
||||
print("OpenIM login failed: \(code) \(msg ?? "")")
|
||||
completion(false)
|
||||
self?.finishLogin(success: false)
|
||||
}
|
||||
}
|
||||
|
||||
private func finishLogin(success: Bool) {
|
||||
isLogining = false
|
||||
let callbacks = pendingLoginCompletions
|
||||
pendingLoginCompletions.removeAll()
|
||||
callbacks.forEach { $0(success) }
|
||||
}
|
||||
|
||||
// MARK: - Logout
|
||||
func logout(completion: ((Bool) -> Void)? = nil) {
|
||||
OIMManager.manager.logoutWith(onSuccess: { (_: String?) in
|
||||
completion?(true)
|
||||
}, onFailure: { (code: Int, msg: String?) in
|
||||
print("OpenIM logout failed: \(code) \(msg ?? "")")
|
||||
completion?(false)
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - Get Joined Groups
|
||||
func getJoinedGroups(completion: @escaping ([OIMGroupInfo]) -> Void) {
|
||||
print("GroupIMService: getJoinedGroups called, isLogined=\(isLogined)")
|
||||
OIMManager.manager.getJoinedGroupListWith(onSuccess: { groups in
|
||||
print("GroupIMService: getJoinedGroups success, count=\(groups?.count ?? 0)")
|
||||
completion(groups ?? [])
|
||||
}, onFailure: { code, msg in
|
||||
print("GroupIMService: getJoinedGroups failed: \(code) \(msg ?? "")")
|
||||
|
|
@ -79,6 +119,36 @@ final class GroupIMService {
|
|||
conversationListener = ConversationListenerProxy(handler: handler)
|
||||
OIMManager.callbacker.addConversationListener(listener: conversationListener!)
|
||||
}
|
||||
|
||||
// MARK: - Group Listener
|
||||
private var groupListener: GroupListenerProxy?
|
||||
|
||||
/// 监听群组变化(加群/退群/群信息变更),用于 SDK 同步到新群时刷新列表
|
||||
func setGroupListener(_ handler: @escaping () -> Void) {
|
||||
groupListener = GroupListenerProxy(handler: handler)
|
||||
OIMManager.callbacker.addGroupListener(listener: groupListener!)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - GroupListenerProxy
|
||||
private class GroupListenerProxy: NSObject, OIMGroupListener {
|
||||
private let handler: () -> Void
|
||||
|
||||
init(handler: @escaping () -> Void) {
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
func onJoinedGroupAdded(_ groupInfo: OIMGroupInfo) {
|
||||
handler()
|
||||
}
|
||||
|
||||
func onJoinedGroupDeleted(_ groupInfo: OIMGroupInfo) {
|
||||
handler()
|
||||
}
|
||||
|
||||
func onGroupInfoChanged(_ changeInfo: OIMGroupInfo) {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ConversationListenerProxy
|
||||
|
|
|
|||
|
|
@ -84,6 +84,24 @@ class GroupSettingVC: BaseViewController {
|
|||
guard let model = self.viewModel.groupModel else { return }
|
||||
AppRouter.push(Route.inviteJoin, userInfo: ["groupInfo": model.toJSON()])
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
// 解散圈子
|
||||
rootView.dismissGroupView.rx.tapGesture.subscribe(onNext: { _ in
|
||||
VerificationPopView.show {
|
||||
|
||||
}
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
// 退出圈子
|
||||
rootView.leaveGroupView.rx.tapGesture.subscribe(onNext: { _ in
|
||||
self.showConfirmPop(title: "确定要离开这个圈子吗?",
|
||||
message: "您将无法再与此圈子成员分享位置。确定要离开吗?",
|
||||
confirmText: "否",
|
||||
confirmBlock: { },
|
||||
cancelText: "是") {
|
||||
self.requestLeaveGroup()
|
||||
}
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
// MARK: - API
|
||||
|
|
@ -136,6 +154,16 @@ class GroupSettingVC: BaseViewController {
|
|||
self.requestGroupInfo()
|
||||
}.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func requestLeaveGroup() {
|
||||
DLToast.showLoading()
|
||||
GroupService.leave(requestData: ["group_key": viewModel.groupId]).subscribe { response in
|
||||
DLToast.dismiss()
|
||||
DLToast.show(text: "成功退出") {
|
||||
AppRouter.shared.popToRoot()
|
||||
}
|
||||
}.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
init(groupId: String) {
|
||||
|
|
|
|||
|
|
@ -35,12 +35,35 @@ final class GroupViewController: BaseViewController {
|
|||
}
|
||||
|
||||
// MARK: - OpenIM
|
||||
private func setupOpenIM() {
|
||||
GroupIMService.shared.initSDK()
|
||||
GroupIMService.shared.login { [weak self] success in
|
||||
guard success else { return }
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
self?.requestIMGroups(retryCount: 3)
|
||||
private var hasSetupIMListeners = false
|
||||
|
||||
/// 进入页面加载 IM 数据:确保登录后并行拉取群列表 + 会话
|
||||
private func loadIMData() {
|
||||
GroupIMService.shared.ensureLogin { [weak self] success in
|
||||
guard let self = self else { return }
|
||||
guard success else {
|
||||
return
|
||||
}
|
||||
self.setupIMListenersIfNeeded()
|
||||
self.refreshIMData()
|
||||
}
|
||||
}
|
||||
|
||||
/// 注册 IM 监听(仅一次):会话变化更新未读/时间;群组变化(SDK 同步到新群/退群)刷新列表
|
||||
private func setupIMListenersIfNeeded() {
|
||||
guard !hasSetupIMListeners else { return }
|
||||
hasSetupIMListeners = true
|
||||
|
||||
GroupIMService.shared.setConversationListener { [weak self] _ in
|
||||
GroupIMService.shared.getConversationList { conversations in
|
||||
self?.viewModel.updateConversations(conversations)
|
||||
}
|
||||
}
|
||||
|
||||
// 群组同步完成(如刚创建/加入的群)后刷新群列表,解决业务接口先返回、SDK 后同步的时序问题
|
||||
GroupIMService.shared.setGroupListener { [weak self] in
|
||||
GroupIMService.shared.getJoinedGroups { groups in
|
||||
self?.viewModel.loadIMGroups(groups)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -63,26 +86,6 @@ final class GroupViewController: BaseViewController {
|
|||
group.notify(queue: .main) { [weak self] in
|
||||
DLToast.dismiss()
|
||||
self?.viewModel.loadIMGroups(groups, conversations: conversations)
|
||||
|
||||
// GroupIMService.shared.setConversationListener { [weak self] _ in
|
||||
// GroupIMService.shared.getConversationList { conversations in
|
||||
// self?.viewModel.updateConversations(conversations)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private func requestIMGroups(retryCount: Int = 3) {
|
||||
DLToast.showLoading(text: "消息获取中...")
|
||||
GroupIMService.shared.getJoinedGroups { [weak self] groups in
|
||||
guard let self = self else { return }
|
||||
if groups.isEmpty && retryCount > 0 {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||
self.requestIMGroups(retryCount: retryCount - 1)
|
||||
}
|
||||
} else {
|
||||
self.refreshIMData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -124,13 +127,7 @@ final class GroupViewController: BaseViewController {
|
|||
rootView.joinedTabLabel.rx.tapGesture
|
||||
.subscribe(onNext: { [weak self] _ in self?.switchToSegment(1) })
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
NotificationCenter.default.rx.notification(.RefreshGroupInfoNotification)
|
||||
.subscribe(onNext: { [weak self] _ in
|
||||
self?.requestIMGroups()
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
|
||||
Observable.merge(
|
||||
rootView.createdTableView.rx.modelSelected(GroupCellData.self).asObservable(),
|
||||
rootView.joinedTableView.rx.modelSelected(GroupCellData.self).asObservable()
|
||||
|
|
@ -140,6 +137,16 @@ final class GroupViewController: BaseViewController {
|
|||
AppRouter.push(Route.groupChat, userInfo: ["groupId": groupId])
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
NotificationCenter.default.rx.notification(.RefreshUserConfigNotification)
|
||||
.subscribe(onNext: { [weak self] _ in
|
||||
self?.requestRecommandGroup()
|
||||
self?.requestGroupInfo()
|
||||
|
||||
guard let config = AppContextManager.shared.systemConfig else { return }
|
||||
self?.rootView.cycleScrollView.imageURLStringsGroup = config.groupBannerList
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func switchToSegment(_ index: Int) {
|
||||
|
|
@ -203,7 +210,7 @@ final class GroupViewController: BaseViewController {
|
|||
GroupService.groupInfo().subscribe { response in
|
||||
guard let model = response.model else { return }
|
||||
self.viewModel.groupList = model.groups
|
||||
self.setupOpenIM()
|
||||
self.loadIMData()
|
||||
}.disposed(by: disposeBag)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
//
|
||||
// ReviewMemberListVC.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
import RxDataSources
|
||||
import ObjectMapper
|
||||
|
||||
class ReviewMemberListVC: BaseViewController {
|
||||
|
||||
fileprivate var rootView: ReviewMemberListView!
|
||||
|
||||
override func loadView() {
|
||||
rootView = ReviewMemberListView(frame: UIScreen.main.bounds)
|
||||
view = rootView
|
||||
}
|
||||
|
||||
private var viewModel: ReviewMemberListViewModel
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
bindViewModel()
|
||||
|
||||
requestReviewlist()
|
||||
}
|
||||
|
||||
private func bindViewModel() {
|
||||
viewModel.output.sectionedItems
|
||||
.bind(to: rootView.tableView.rx.items(dataSource: dataSource))
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
lazy private var dataSource: RxTableViewSectionedReloadDataSource<ReviewMemberListSectionModel> = {
|
||||
return RxTableViewSectionedReloadDataSource<ReviewMemberListSectionModel>(
|
||||
configureCell: { (_, tableView, indexPath, model) in
|
||||
let cell: ReviewMemberCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
cell.configure(model)
|
||||
return cell
|
||||
})
|
||||
}()
|
||||
|
||||
// MARK: - API
|
||||
private func requestReviewlist() {
|
||||
DLToast.showLoading()
|
||||
GroupService.reviewlist(requestData: ["group_key": viewModel.groupId]).subscribe { response in
|
||||
DLToast.dismiss()
|
||||
|
||||
}.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
init(groupId: String) {
|
||||
self.viewModel = ReviewMemberListViewModel(groupId: groupId)
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// ReviewMemberListVM.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/10.
|
||||
//
|
||||
|
||||
import RxSwift
|
||||
import RxDataSources
|
||||
import ObjectMapper
|
||||
|
||||
typealias ReviewMemberListSectionModel = SectionModel<String, GroupMemberModel>
|
||||
|
||||
struct ReviewMemberListViewModel {
|
||||
|
||||
let groupId: String
|
||||
|
||||
var groupModel: GroupInfoModel?
|
||||
|
||||
struct Output {
|
||||
var sectionedItems: Observable<[ReviewMemberListSectionModel]>
|
||||
}
|
||||
|
||||
let output: Output
|
||||
|
||||
private var disposeBag = DisposeBag()
|
||||
private let sectionedItems = PublishSubject<[ReviewMemberListSectionModel]>()
|
||||
|
||||
init(groupId: String) {
|
||||
self.groupId = groupId
|
||||
output = Output(
|
||||
sectionedItems: sectionedItems.asObservable()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
//
|
||||
// ReviewMemberListView.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
|
||||
class ReviewMemberListView: UIView {
|
||||
|
||||
var disposeBag = DisposeBag()
|
||||
|
||||
private func setupRx() {
|
||||
backBtn.rx.tap.subscribe(onNext: { _ in
|
||||
AppRouter.shared.popOrDismiss()
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
addSubview(navBgView)
|
||||
addSubview(navBarView)
|
||||
navBarView.addSubview(navTitleLabel)
|
||||
navBarView.addSubview(backBtn)
|
||||
|
||||
addSubview(cornerBgView)
|
||||
addSubview(tableView)
|
||||
|
||||
navBgView.layoutChain
|
||||
.edges(excludingEdge: .bottom)
|
||||
.heightToWidth(160/375)
|
||||
|
||||
navBarView.layoutChain
|
||||
.edges(excludingEdge: .bottom)
|
||||
.height(kNaviHeight)
|
||||
|
||||
navTitleLabel.layoutChain
|
||||
.top(kStatusBarHeight + 12)
|
||||
.centerY(backBtn)
|
||||
.centerX()
|
||||
|
||||
backBtn.layoutChain
|
||||
.centerY(navTitleLabel)
|
||||
.left(15)
|
||||
.width(24)
|
||||
.height(24)
|
||||
|
||||
cornerBgView.layoutChain
|
||||
.edges(excludingEdge: .bottom)
|
||||
.bottom(kSafeBottomMargin + 10, relation: .greaterThanOrEqual)
|
||||
|
||||
tableView.layoutChain
|
||||
.edges(excludingEdge: .bottom)
|
||||
.bottom(kSafeBottomMargin + 10, relation: .greaterThanOrEqual)
|
||||
}
|
||||
|
||||
lazy var navBgView: UIImageView = {
|
||||
let iv = UIImageView()
|
||||
iv.image = UIImage(named: "Common/navBar_bg_2")
|
||||
iv.contentMode = .scaleAspectFill
|
||||
return iv
|
||||
}()
|
||||
|
||||
lazy var navBarView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var navTitleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = "待审核成员"
|
||||
label.font = .systemFont(ofSize: 18, weight: .medium)
|
||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var backBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setImage(UIImage(named: "Common/back"), for: .normal)
|
||||
btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100)
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var cornerBgView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = UIColor(hexStr: "#F5FBFF")
|
||||
view.cornerRadius = 10
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var tableView: UITableView = {
|
||||
let tableView = UITableView(frame: .zero, style: .plain)
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.separatorStyle = .none
|
||||
tableView.estimatedRowHeight = 77
|
||||
tableView.register(ReviewMemberCell.self)
|
||||
return tableView
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: .zero)
|
||||
backgroundColor = .white
|
||||
setupUI()
|
||||
setupRx()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ReviewMemberCell
|
||||
class ReviewMemberCell: UITableViewCell {
|
||||
|
||||
func configure(_ model: GroupMemberModel) {
|
||||
|
||||
}
|
||||
|
||||
override init(style: CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
private func setupSubviews() {
|
||||
contentView.addSubview(bgView)
|
||||
bgView.addSubview(avaterImgView)
|
||||
bgView.addSubview(nameLab)
|
||||
bgView.addSubview(agreeBtn)
|
||||
bgView.addSubview(refuseBtn)
|
||||
|
||||
bgView.layoutChain
|
||||
.edgesHorzontal(15)
|
||||
.edgesVertical()
|
||||
|
||||
avaterImgView.layoutChain
|
||||
.edgesVertical(20)
|
||||
.left(15)
|
||||
.height(45)
|
||||
.widthToHeight(1)
|
||||
|
||||
nameLab.layoutChain
|
||||
.leftToRightOfView(avaterImgView, offset: 10)
|
||||
.centerY(avaterImgView)
|
||||
|
||||
agreeBtn.layoutChain
|
||||
.centerY()
|
||||
.right(15)
|
||||
.width(34)
|
||||
.height(15)
|
||||
|
||||
refuseBtn.layoutChain
|
||||
.centerY()
|
||||
.rightToLeftOfView(agreeBtn, offset: 8)
|
||||
.width(34)
|
||||
.height(15)
|
||||
}
|
||||
|
||||
lazy var bgView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear//UIColor(hexStr: "#F5FBFF")
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var avaterImgView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.backgroundColor = .clear
|
||||
view.cornerRadius = 22.5
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var nameLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = UIColor(hexStr: "#0F2846")
|
||||
label.font = .systemFont(ofSize: 14, weight: .semibold)
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var agreeBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setTitle("同意", for: .normal)
|
||||
btn.setTitleColor(.white, for: .normal)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 10, weight: .regular)
|
||||
btn.setBackgroundColor(UIColor(hexStr: "#16B3FF"), for: .normal)
|
||||
btn.cornerRadius = 7.5
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var refuseBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setTitle("拒绝", for: .normal)
|
||||
btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 10, weight: .regular)
|
||||
btn.setBackgroundColor(.white, for: .normal)
|
||||
btn.borderWidth = 0.5
|
||||
btn.borderColor = UIColor(hexStr: "#16B3FF")
|
||||
btn.cornerRadius = 7.5
|
||||
return btn
|
||||
}()
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
// Initialization code
|
||||
}
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
super.setSelected(selected, animated: animated)
|
||||
|
||||
// Configure the view for the selected state
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,380 @@
|
|||
//
|
||||
// VerificationPopView.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
import IQKeyboardManagerSwift
|
||||
|
||||
class VerificationPopView: UIView {
|
||||
|
||||
private static let shared = VerificationPopView(frame: CGRect(origin: .zero, size: kScreenSize))
|
||||
|
||||
var disposeBag = DisposeBag()
|
||||
|
||||
private var completion: (() -> Void)?
|
||||
|
||||
static func show(completion: @escaping (() -> Void)) {
|
||||
guard let superView = kKeyWindow else {
|
||||
return
|
||||
}
|
||||
|
||||
if VerificationPopView.shared.superview != nil {
|
||||
VerificationPopView.shared.removeFromSuperview()
|
||||
VerificationPopView.shared.bgView.frame = .zero
|
||||
}
|
||||
|
||||
VerificationPopView.shared.num1Lab.text = "\(Int.random(in: 1...9))"
|
||||
VerificationPopView.shared.num2Lab.text = "\(Int.random(in: 1...9))"
|
||||
VerificationPopView.shared.resultTF.text = ""
|
||||
|
||||
VerificationPopView.shared.bgView.alpha = 1
|
||||
VerificationPopView.shared.bgView.frame = CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight)
|
||||
superView.addSubview(VerificationPopView.shared)
|
||||
superView.bringSubviewToFront(VerificationPopView.shared)
|
||||
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
VerificationPopView.shared.bgView.alpha = 1
|
||||
} completion: { _ in
|
||||
VerificationPopView.shared.resultTF.becomeFirstResponder()
|
||||
}
|
||||
|
||||
VerificationPopView.shared.completion = {
|
||||
completion()
|
||||
VerificationPopView.dismiss()
|
||||
}
|
||||
|
||||
IQKeyboardManager.shared.resignOnTouchOutside = false
|
||||
}
|
||||
|
||||
/// 关闭
|
||||
static func dismiss() {
|
||||
guard VerificationPopView.shared.superview != nil else { return }
|
||||
|
||||
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseIn]) {
|
||||
VerificationPopView.shared.bgView.alpha = 0
|
||||
} completion: { _ in
|
||||
VerificationPopView.shared.removeFromSuperview()
|
||||
IQKeyboardManager.shared.resignOnTouchOutside = true
|
||||
}
|
||||
}
|
||||
|
||||
private func setupRx() {
|
||||
resultTF.rx.text
|
||||
.bind(to: resultLab.rx.text)
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
resultTF.rx.text.orEmpty
|
||||
.subscribe(onNext: { [weak self] text in
|
||||
guard let self = self,
|
||||
let num1 = self.num1Lab.text,
|
||||
let num2 = self.num2Lab.text else { return }
|
||||
let result = num1.integer + num2.integer
|
||||
|
||||
if !text.isEmpty {
|
||||
self.resultIconView.image = UIImage(named: result == text.integer ? "Popup/correct" : "Popup/error")
|
||||
self.confirmBtn.isEnabled = result == text.integer
|
||||
}
|
||||
else {
|
||||
self.resultIconView.image = nil
|
||||
self.confirmBtn.isEnabled = false
|
||||
}
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
confirmBtn.rx.tap.subscribe(onNext: { _ in
|
||||
self.completion?()
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
closeBtn.rx.tap.subscribe(onNext: { _ in
|
||||
VerificationPopView.dismiss()
|
||||
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private lazy var bgView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .black.withAlphaComponent(0.5)
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var popView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var popBgView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .white
|
||||
view.cornerRadius = 30
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var logoImgView: UIImageView = {
|
||||
let view = UIImageView(image: UIImage(named: "Popup/bell"))
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var headerBgImg: UIImageView = {
|
||||
let view = UIImageView(image: UIImage(named: "Popup/header_bg"))
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var titleLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = "确定要离开这个圈子吗?"
|
||||
label.font = .systemFont(ofSize: 20, weight: .bold)
|
||||
label.textColor = UIColor(hexStr: "#1A1A1A")
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var contentLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = "您离开后将解散圈子,确定 要解散吗?"
|
||||
label.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
label.textColor = UIColor(hexStr: "#767676")
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var tipsLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = "请验证:填写组合数字,相加后需等于给定数值"
|
||||
label.font = .systemFont(ofSize: 8, weight: .medium)
|
||||
label.textColor = UIColor(hexStr: "#999999")
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var equationView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
let addLab = UILabel()
|
||||
addLab.text = "+"
|
||||
addLab.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
addLab.textColor = UIColor(hexStr: "#999999")
|
||||
|
||||
let equalLab = UILabel()
|
||||
equalLab.text = "="
|
||||
equalLab.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
equalLab.textColor = UIColor(hexStr: "#999999")
|
||||
|
||||
view.addSubview(num1View)
|
||||
view.addSubview(addLab)
|
||||
view.addSubview(num2View)
|
||||
view.addSubview(equalLab)
|
||||
view.addSubview(resultView)
|
||||
view.addSubview(resultIconView)
|
||||
|
||||
num1View.layoutChain
|
||||
.left()
|
||||
.edgesVertical()
|
||||
.width(30)
|
||||
.height(30)
|
||||
|
||||
addLab.layoutChain
|
||||
.leftToRightOfView(num1View, offset: 13)
|
||||
.centerY()
|
||||
|
||||
num2View.layoutChain
|
||||
.topToView(num1View)
|
||||
.leftToRightOfView(addLab, offset: 13)
|
||||
.width(30)
|
||||
.height(30)
|
||||
|
||||
equalLab.layoutChain
|
||||
.leftToRightOfView(num2View, offset: 13)
|
||||
.centerY()
|
||||
|
||||
resultView.layoutChain
|
||||
.topToView(num1View)
|
||||
.leftToRightOfView(equalLab, offset: 13)
|
||||
.height(30)
|
||||
.width(30)
|
||||
|
||||
resultIconView.layoutChain
|
||||
.leftToRightOfView(resultView, offset: 10)
|
||||
.centerY()
|
||||
.right()
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var num1View: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = UIColor(hexStr: "#EFF9FF")
|
||||
view.cornerRadius = 2
|
||||
|
||||
view.addSubview(num1Lab)
|
||||
num1Lab.layoutChain
|
||||
.edges()
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var num1Lab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
label.textColor = UIColor(hexStr: "#666666")
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var num2View: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = UIColor(hexStr: "#EFF9FF")
|
||||
view.cornerRadius = 2
|
||||
|
||||
view.addSubview(num2Lab)
|
||||
num2Lab.layoutChain
|
||||
.edges()
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var num2Lab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
label.textColor = UIColor(hexStr: "#666666")
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var resultView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = UIColor(hexStr: "#5CBBFF")
|
||||
view.cornerRadius = 2
|
||||
|
||||
view.addSubview(resultTF)
|
||||
view.addSubview(resultLab)
|
||||
resultLab.layoutChain
|
||||
.edges(all: 4)
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var resultTF: UITextField = {
|
||||
let tf = UITextField()
|
||||
tf.keyboardType = .numberPad
|
||||
tf.returnKeyType = .done
|
||||
tf.isHidden = true
|
||||
return tf
|
||||
}()
|
||||
|
||||
lazy var resultLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
label.textColor = .white//UIColor(hexStr: "#16B3FF")
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var resultIconView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.backgroundColor = .clear
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var confirmBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setTitle("是", for: .normal)
|
||||
btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
btn.backgroundColor = .clear
|
||||
btn.isEnabled = false
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var closeBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setTitle("否", for: .normal)
|
||||
btn.setTitleColor(UIColor(hexStr: "#0F2846"), for: .normal)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||||
btn.cornerRadius = 25
|
||||
return btn
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .clear
|
||||
addSubview(bgView)
|
||||
bgView.addSubview(popView)
|
||||
popView.addSubview(popBgView)
|
||||
popView.addSubview(logoImgView)
|
||||
popBgView.addSubview(headerBgImg)
|
||||
popBgView.addSubview(titleLab)
|
||||
popBgView.addSubview(contentLab)
|
||||
popBgView.addSubview(tipsLab)
|
||||
popBgView.addSubview(equationView)
|
||||
popBgView.addSubview(confirmBtn)
|
||||
popBgView.addSubview(closeBtn)
|
||||
|
||||
popView.layoutChain
|
||||
.centerY(self, offset: -40)
|
||||
.centerX()
|
||||
.width(315)
|
||||
|
||||
popBgView.layoutChain.edges()
|
||||
|
||||
logoImgView.layoutChain
|
||||
.left(16)
|
||||
.top(-45)
|
||||
.width(109)
|
||||
.height(109)
|
||||
|
||||
headerBgImg.layoutChain
|
||||
.edges(excludingEdge: .bottom)
|
||||
.heightToWidth(100/315)
|
||||
|
||||
titleLab.layoutChain
|
||||
.top(53)
|
||||
.centerX()
|
||||
|
||||
contentLab.layoutChain
|
||||
.topToBottomOfView(titleLab, offset: 10)
|
||||
.edgesHorzontal(20)
|
||||
|
||||
tipsLab.layoutChain
|
||||
.topToBottomOfView(contentLab, offset: 8)
|
||||
.centerX()
|
||||
|
||||
equationView.layoutChain
|
||||
.topToBottomOfView(tipsLab, offset: 6)
|
||||
.centerX()
|
||||
.height(30)
|
||||
|
||||
closeBtn.layoutChain
|
||||
.topToBottomOfView(equationView, offset: 20)
|
||||
.edgesHorzontal(20)
|
||||
.height(50)
|
||||
|
||||
confirmBtn.layoutChain
|
||||
.topToBottomOfView(closeBtn, offset: 10)
|
||||
.edgesHorzontal(20)
|
||||
.height(50)
|
||||
.bottom(10)
|
||||
|
||||
setupRx()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -10,6 +10,8 @@ import RxSwift
|
|||
import RxCocoa
|
||||
import RxDataSources
|
||||
import CoreLocation
|
||||
import SwiftyUserDefaults
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
import MAMapKit
|
||||
#endif
|
||||
|
|
@ -51,8 +53,7 @@ class HomeViewController: BaseViewController {
|
|||
setupHeading()
|
||||
reactiveAction()
|
||||
|
||||
requestUserInfo()
|
||||
requestGroupInfo()
|
||||
requestUserConfig()
|
||||
}
|
||||
|
||||
private func reactiveAction() {
|
||||
|
|
@ -81,6 +82,13 @@ class HomeViewController: BaseViewController {
|
|||
}
|
||||
}.disposed(by: disposeBag)
|
||||
|
||||
// User Config刷新
|
||||
NotificationCenter.default.rx.notification(.RefreshUserConfigNotification, object: nil)
|
||||
.subscribe { [weak self] notification in
|
||||
self?.requestUserConfig()
|
||||
}.disposed(by: disposeBag)
|
||||
|
||||
// 圈子刷新
|
||||
NotificationCenter.default.rx.notification(.RefreshGroupInfoNotification, object: nil)
|
||||
.subscribe { [weak self] notification in
|
||||
self?.requestGroupInfo()
|
||||
|
|
@ -110,6 +118,28 @@ class HomeViewController: BaseViewController {
|
|||
}()
|
||||
|
||||
// MARK: - API
|
||||
|
||||
/// 获取用户配置
|
||||
func requestUserConfig() {
|
||||
SystemService.userConfig().subscribe(onNext: { response in
|
||||
guard let model = response.model else { return }
|
||||
Defaults[\.loginToken] = model.token
|
||||
AppContextManager.shared.systemConfig = model.config
|
||||
self.getUserIMToken()
|
||||
self.requestUserInfo()
|
||||
self.requestGroupInfo()
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
/// 获取用户IM Token
|
||||
func getUserIMToken() {
|
||||
UserService.imToken().subscribe(onNext: { response in
|
||||
guard let data = response.data, let token = data["token"] as? String else { return }
|
||||
AppContextManager.shared.imToken = token
|
||||
GroupIMService.shared.login { _ in }
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func requestUserInfo() {
|
||||
UserService.userInfo().subscribe { response in
|
||||
guard let model = response.model else { return }
|
||||
|
|
|
|||
|
|
@ -29,11 +29,14 @@ class LaunchViewController: BaseViewController {
|
|||
view.backgroundColor = UIColor(hexStr: "#E0F2FF")
|
||||
setupLayout()
|
||||
|
||||
GroupIMService.shared.initSDK()
|
||||
|
||||
experienceBtn.rx.tap.subscribe(onNext: { _ in
|
||||
// Defaults[\.guideShowVersion] = self.showVersion
|
||||
AppDelegate.shared.showMainViewController()
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
|
||||
getUserConfig()
|
||||
|
||||
navigateAfterDelay()
|
||||
|
|
@ -65,11 +68,11 @@ class LaunchViewController: BaseViewController {
|
|||
/// 获取用户配置
|
||||
func getUserConfig() {
|
||||
SystemService.userConfig().subscribe(onNext: { response in
|
||||
self.getUserIMToken()
|
||||
guard let model = response.model else { return }
|
||||
Defaults[\.loginToken] = model.token
|
||||
AppContextManager.shared.systemConfig = model.config
|
||||
// 保存用户数据
|
||||
AppContextManager.shared.saveAccount(model)
|
||||
// AppContextManager.shared.saveAccount(model)
|
||||
}, onError: { [weak self] (error) in
|
||||
DLAlert.show(title: error.localizedDescription,
|
||||
defaultTitle: "重试") { [weak self] in
|
||||
|
|
@ -79,16 +82,6 @@ class LaunchViewController: BaseViewController {
|
|||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
/// 获取用户IM Token
|
||||
func getUserIMToken() {
|
||||
UserService.imToken().subscribe(onNext: { response in
|
||||
guard let data = response.data, let token = data["token"] as? String else { return }
|
||||
AppContextManager.shared.imToken = token
|
||||
// 提前初始化 SDK,减少圈子页等待时间
|
||||
GroupIMService.shared.initSDK()
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
init() {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ class LoginView: UIView {
|
|||
btn.setTitleColor(.white, for: .normal)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
btn.setBackgroundImage(UIImage(named: "Login/visitor_bg"), for: .normal)
|
||||
btn.isHidden = true
|
||||
return btn
|
||||
}()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import Foundation
|
||||
import RxSwift
|
||||
import SwiftyUserDefaults
|
||||
#if !targetEnvironment(simulator)
|
||||
import GeYanSdk
|
||||
#endif
|
||||
|
|
@ -35,17 +36,13 @@ final class LoginViewModel: BaseViewModel {
|
|||
func loginAction(type: String, data: [String : Any]) {
|
||||
DLToast.showLoading()
|
||||
UserService.login(type: type, data: data).subscribe { response in
|
||||
GroupIMService.shared.logout()
|
||||
DLToast.dismiss()
|
||||
guard let model = response.model else { return }
|
||||
if AppContextManager.shared.saveAccount(model) {
|
||||
NotificationCenter.default.post(name: .RefreshGroupInfoNotification,
|
||||
object: nil,
|
||||
userInfo: nil)
|
||||
DLToast.showSuccess(text: "登录成功") {
|
||||
AppRouter.shared.popToRoot()
|
||||
}
|
||||
}
|
||||
else {
|
||||
DLToast.show(text: "登录失败,请重新尝试")
|
||||
Defaults[\.loginToken] = model.token
|
||||
DLToast.showSuccess(text: "登录成功") {
|
||||
NotificationCenter.default.post(name: .RefreshUserConfigNotification, object: nil)
|
||||
AppRouter.shared.popOrDismiss()
|
||||
}
|
||||
}.disposed(by: disposeBag)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// AccountVC.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
import RxDataSources
|
||||
|
||||
class AccountVC: BaseViewController {
|
||||
|
||||
fileprivate var rootView: AccountView!
|
||||
|
||||
override func loadView() {
|
||||
rootView = AccountView(frame: UIScreen.main.bounds)
|
||||
view = rootView
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
|
||||
reactiveAction()
|
||||
}
|
||||
|
||||
private func reactiveAction() {
|
||||
rootView.logoutBtn.rx.tap.subscribe(onNext: { _ in
|
||||
self.showConfirmPop(title: "温馨提醒",
|
||||
message: "确定要退出账号吗?",
|
||||
confirmText: "否",
|
||||
confirmBlock: { },
|
||||
cancelText: "是") {
|
||||
self.requestLogout()
|
||||
}
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
// MARK: - API
|
||||
private func requestLogout() {
|
||||
DLToast.showLoading()
|
||||
UserService.logout().subscribe { response in
|
||||
DLToast.dismiss()
|
||||
AppContextManager.shared.deleteAccount()
|
||||
GroupIMService.shared.logout()
|
||||
AppDelegate.shared.showMainViewController()
|
||||
}.disposed(by: disposeBag)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,319 @@
|
|||
//
|
||||
// AccountView.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
|
||||
class AccountView: UIView {
|
||||
|
||||
var disposeBag = DisposeBag()
|
||||
|
||||
private func setupRx() {
|
||||
backBtn.rx.tap.subscribe(onNext: { _ in
|
||||
AppRouter.shared.popOrDismiss()
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
addSubview(navBgView)
|
||||
addSubview(navBarView)
|
||||
navBarView.addSubview(navTitleLabel)
|
||||
navBarView.addSubview(backBtn)
|
||||
|
||||
addSubview(infoView)
|
||||
infoView.addSubview(infoStackView)
|
||||
addSubview(otherInfoView)
|
||||
otherInfoView.addSubview(otherInfoStackView)
|
||||
|
||||
addSubview(logoutBtn)
|
||||
addSubview(cancelledBtn)
|
||||
|
||||
navBgView.layoutChain
|
||||
.edges(excludingEdge: .bottom)
|
||||
.heightToWidth(160/375)
|
||||
|
||||
navBarView.layoutChain
|
||||
.edges(excludingEdge: .bottom)
|
||||
.height(kNaviHeight)
|
||||
|
||||
navTitleLabel.layoutChain
|
||||
.top(kStatusBarHeight + 12)
|
||||
.centerY(backBtn)
|
||||
.centerX()
|
||||
|
||||
backBtn.layoutChain
|
||||
.centerY(navTitleLabel)
|
||||
.left(15)
|
||||
.width(24)
|
||||
.height(24)
|
||||
|
||||
infoView.layoutChain
|
||||
.topToBottomOfView(navBarView, offset: 18)
|
||||
.edgesHorzontal(16)
|
||||
|
||||
infoStackView.layoutChain.edges()
|
||||
|
||||
userIdView.layoutChain.height(52)
|
||||
phoneView.layoutChain.height(52)
|
||||
|
||||
otherInfoView.layoutChain
|
||||
.topToBottomOfView(infoView, offset: 18)
|
||||
.edgesHorzontal(16)
|
||||
|
||||
otherInfoStackView.layoutChain.edges()
|
||||
|
||||
versionView.layoutChain.height(52)
|
||||
clearView.layoutChain.height(52)
|
||||
|
||||
logoutBtn.layoutChain
|
||||
.edgesHorzontal(30)
|
||||
.bottomToTopOfView(cancelledBtn, offset: -10)
|
||||
.height(50)
|
||||
|
||||
cancelledBtn.layoutChain
|
||||
.edgesHorzontal(30)
|
||||
.bottom(kSafeBottomMargin + 10)
|
||||
.height(50)
|
||||
}
|
||||
|
||||
lazy var navBgView: UIImageView = {
|
||||
let iv = UIImageView()
|
||||
iv.image = UIImage(named: "Common/navBar_bg_2")
|
||||
iv.contentMode = .scaleAspectFill
|
||||
return iv
|
||||
}()
|
||||
|
||||
lazy var navBarView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var navTitleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = "账号与安全"
|
||||
label.font = .systemFont(ofSize: 18, weight: .medium)
|
||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var backBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setImage(UIImage(named: "Common/back"), for: .normal)
|
||||
btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100)
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var infoView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
view.layer.shadowColor = UIColor(hexStr: "#0F2846", alpha: 0.1).cgColor
|
||||
view.layer.shadowOffset = CGSize(width: 0, height: 0)
|
||||
view.layer.shadowOpacity = 1
|
||||
view.layer.shadowRadius = 10
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var infoStackView: UIStackView = {
|
||||
let view = UIStackView(arrangedSubviews: [userIdView, phoneView])
|
||||
view.axis = .vertical
|
||||
view.alignment = .fill
|
||||
view.distribution = .fill
|
||||
view.spacing = 0
|
||||
view.backgroundColor = .white
|
||||
view.cornerRadius = 10
|
||||
return view
|
||||
}()
|
||||
|
||||
/// UserID
|
||||
lazy var userIdView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
let titleLab = UILabel()
|
||||
titleLab.text = "ID"
|
||||
titleLab.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
titleLab.textColor = UIColor(hexStr: "#1A1A1A")
|
||||
view.addSubview(titleLab)
|
||||
titleLab.layoutChain
|
||||
.left(15)
|
||||
.centerY()
|
||||
|
||||
let contentLab = UILabel()
|
||||
contentLab.text = AppContextManager.shared.userId
|
||||
contentLab.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
contentLab.textColor = UIColor(hexStr: "#1A1A1A")
|
||||
view.addSubview(contentLab)
|
||||
contentLab.layoutChain
|
||||
.right(15)
|
||||
.centerY()
|
||||
|
||||
let line = UIView()
|
||||
line.backgroundColor = ThemeManager.shared.color.lineColor
|
||||
view.addSubview(line)
|
||||
line.layoutChain
|
||||
.bottom()
|
||||
.height(0.5)
|
||||
.edgesHorzontal(15)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
/// 手机号
|
||||
lazy var phoneView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
let titleLab = UILabel()
|
||||
titleLab.text = "手机号"
|
||||
titleLab.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
titleLab.textColor = UIColor(hexStr: "#1A1A1A")
|
||||
view.addSubview(titleLab)
|
||||
titleLab.layoutChain
|
||||
.left(15)
|
||||
.centerY()
|
||||
|
||||
let arrow = UIImageView(image: UIImage(named: "Group/arrow"))
|
||||
view.addSubview(arrow)
|
||||
arrow.layoutChain
|
||||
.right(15)
|
||||
.width(14)
|
||||
.height(14)
|
||||
.centerY()
|
||||
|
||||
view.addSubview(phoneLab)
|
||||
phoneLab.layoutChain
|
||||
.rightToLeftOfView(arrow, offset: -8)
|
||||
.centerY()
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var phoneLab: UILabel = {
|
||||
let lab = UILabel()
|
||||
lab.text = AppContextManager.shared.userPhone
|
||||
lab.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
lab.textColor = UIColor(hexStr: "#1A1A1A")
|
||||
return lab
|
||||
}()
|
||||
|
||||
// 其它
|
||||
lazy var otherInfoView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
view.layer.shadowColor = UIColor(hexStr: "#0F2846", alpha: 0.1).cgColor
|
||||
view.layer.shadowOffset = CGSize(width: 0, height: 0)
|
||||
view.layer.shadowOpacity = 1
|
||||
view.layer.shadowRadius = 10
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var otherInfoStackView: UIStackView = {
|
||||
let view = UIStackView(arrangedSubviews: [versionView, clearView])
|
||||
view.axis = .vertical
|
||||
view.alignment = .fill
|
||||
view.distribution = .fill
|
||||
view.spacing = 0
|
||||
view.backgroundColor = .white
|
||||
view.cornerRadius = 10
|
||||
return view
|
||||
}()
|
||||
|
||||
/// 当前版本
|
||||
lazy var versionView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
let titleLab = UILabel()
|
||||
titleLab.text = "当前版本"
|
||||
titleLab.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
titleLab.textColor = UIColor(hexStr: "#1A1A1A")
|
||||
view.addSubview(titleLab)
|
||||
titleLab.layoutChain
|
||||
.left(15)
|
||||
.centerY()
|
||||
|
||||
let contentLab = UILabel()
|
||||
contentLab.text = kAppShortVersion
|
||||
contentLab.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
contentLab.textColor = UIColor(hexStr: "#1A1A1A")
|
||||
view.addSubview(contentLab)
|
||||
contentLab.layoutChain
|
||||
.right(15)
|
||||
.centerY()
|
||||
|
||||
let line = UIView()
|
||||
line.backgroundColor = ThemeManager.shared.color.lineColor
|
||||
view.addSubview(line)
|
||||
line.layoutChain
|
||||
.bottom()
|
||||
.height(0.5)
|
||||
.edgesHorzontal(15)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
/// 清除缓存
|
||||
lazy var clearView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
let titleLab = UILabel()
|
||||
titleLab.text = "清除缓存"
|
||||
titleLab.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
titleLab.textColor = UIColor(hexStr: "#1A1A1A")
|
||||
view.addSubview(titleLab)
|
||||
titleLab.layoutChain
|
||||
.left(15)
|
||||
.centerY()
|
||||
|
||||
let arrow = UIImageView(image: UIImage(named: "Group/arrow"))
|
||||
view.addSubview(arrow)
|
||||
arrow.layoutChain
|
||||
.right(15)
|
||||
.width(14)
|
||||
.height(14)
|
||||
.centerY()
|
||||
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var logoutBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setTitle("退出登录", for: .normal)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
btn.setTitleColor(.white, for: .normal)
|
||||
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||||
btn.cornerRadius = 25
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var cancelledBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setTitle("注销账号", for: .normal)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
btn.setTitleColor(UIColor(hexStr: "#767676"), for: .normal)
|
||||
btn.backgroundColor = .clear
|
||||
return btn
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: .zero)
|
||||
backgroundColor = UIColor(hexStr: "#FAFAFA")
|
||||
setupUI()
|
||||
setupRx()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,6 +28,20 @@ final class MineView: UIView {
|
|||
private let subtitleColor = UIColor(hexStr: "#767676")
|
||||
private let vipBtnTextColor = UIColor(hexStr: "#00343B")
|
||||
|
||||
func setupData(_ model: UserConfigModel) {
|
||||
|
||||
guard model.vip > 1 else {
|
||||
vipTitleLab.text = "开通会员"
|
||||
vipContentLab.text = "会员尊享10+VIP特权"
|
||||
vipActivateButton.setTitle("立即开通", for: .normal)
|
||||
return
|
||||
}
|
||||
vipTitleLab.text = model.vip_name
|
||||
vipContentLab.text = model.vip == 3 ? "会员终身有效"
|
||||
: "会员到期:\(self.getDateInterval2String(date: model.vip_expire_time, dateFormat: "yyyy.MM.dd"))"
|
||||
vipActivateButton.setTitle("点击续费", for: .normal)
|
||||
}
|
||||
|
||||
private func setupRx() {
|
||||
avatarImageView.rx.tapGesture.subscribe { _ in
|
||||
AppRouter.push(Route.login)
|
||||
|
|
@ -64,7 +78,7 @@ final class MineView: UIView {
|
|||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 16, weight: .heavy)
|
||||
label.textColor = titleColor
|
||||
label.text = "肖战大王叫我来巡山"
|
||||
label.text = " "
|
||||
return label
|
||||
}()
|
||||
|
||||
|
|
@ -79,7 +93,7 @@ final class MineView: UIView {
|
|||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
label.textColor = subtitleColor
|
||||
label.text = "ID:1234567"
|
||||
label.text = " "
|
||||
return label
|
||||
}()
|
||||
|
||||
|
|
|
|||
|
|
@ -35,15 +35,16 @@ final class MineViewController: BaseViewController {
|
|||
bindViewModel()
|
||||
reactiveAction()
|
||||
|
||||
rootView.avatarImageView.image = AppContextManager.shared.avaterIcon
|
||||
rootView.nameLabel.text = AppContextManager.shared.name
|
||||
rootView.idLabel.text = "ID:" + AppContextManager.shared.userId
|
||||
|
||||
viewModel.loadMenuData()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
rootView.avatarImageView.image = AppContextManager.shared.avaterIcon
|
||||
rootView.nameLabel.text = AppContextManager.shared.name
|
||||
rootView.idLabel.text = "ID:" + AppContextManager.shared.userId
|
||||
requestUserInfo()
|
||||
}
|
||||
|
||||
// MARK: - Bindings
|
||||
|
|
@ -68,7 +69,7 @@ final class MineViewController: BaseViewController {
|
|||
private func reactiveAction() {
|
||||
rootView.vipActivateButton.rx.tap
|
||||
.subscribe(onNext: { [weak self] in
|
||||
// TODO: Navigate to VIP activation
|
||||
AppRouter.push(Route.vipRecharge)
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
|
|
@ -85,10 +86,6 @@ final class MineViewController: BaseViewController {
|
|||
DLToast.showSuccess(text: "已复制")
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
rootView.onMenuTap = { [weak self] index in
|
||||
self?.handleMenuTap(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
|
|
@ -100,29 +97,17 @@ final class MineViewController: BaseViewController {
|
|||
return cell
|
||||
})
|
||||
}()
|
||||
|
||||
private func handleMenuTap(at index: Int) {
|
||||
switch index {
|
||||
case 0:
|
||||
// TODO: Account & security
|
||||
break
|
||||
case 1:
|
||||
// TODO: Status setting
|
||||
break
|
||||
case 2:
|
||||
// TODO: Emergency contact
|
||||
break
|
||||
case 3:
|
||||
// TODO: Customer service
|
||||
break
|
||||
case 4:
|
||||
// TODO: Privacy & agreement
|
||||
break
|
||||
case 5:
|
||||
// TODO: Permission check
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
private func requestUserInfo() {
|
||||
UserService.userInfo().subscribe { response in
|
||||
guard let model = response.model else { return }
|
||||
if AppContextManager.shared.saveAccount(model) {
|
||||
self.rootView.avatarImageView.image = model.userIcon
|
||||
self.rootView.nameLabel.text = model.name
|
||||
self.rootView.idLabel.text = "ID:\(model.uid ?? "")"
|
||||
self.rootView.setupData(model)
|
||||
}
|
||||
|
||||
}.disposed(by: disposeBag)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,12 @@ class MineViewModel {
|
|||
|
||||
lazy var cellAction: Action<MineMenuItem, Void> = { this in
|
||||
return Action { model in
|
||||
|
||||
switch model.title {
|
||||
case "账户与安全":
|
||||
AppRouter.push(Route.account)
|
||||
default:
|
||||
break
|
||||
}
|
||||
return .empty()
|
||||
}
|
||||
}(self)
|
||||
|
|
|
|||
|
|
@ -95,4 +95,24 @@ struct GroupService {
|
|||
.map(GroupInfoResponse.self)
|
||||
.asObservable()
|
||||
}
|
||||
|
||||
/// 审核列表
|
||||
/// - Parameters:
|
||||
/// - requestData:group_key
|
||||
static func reviewlist(requestData: [String: Any]) -> Observable<GroupInfoResponse> {
|
||||
let api = GroupAPI.operate(opType: "reviewlist", requestData: requestData).multiTarget
|
||||
return APIProvider.request(token: api)
|
||||
.map(GroupInfoResponse.self)
|
||||
.asObservable()
|
||||
}
|
||||
|
||||
/// 退出群聊
|
||||
/// - Parameters:
|
||||
/// - requestData:group_key
|
||||
static func leave(requestData: [String: Any]) -> Observable<GroupInfoResponse> {
|
||||
let api = GroupAPI.operate(opType: "leave", requestData: requestData).multiTarget
|
||||
return APIProvider.request(token: api)
|
||||
.map(GroupInfoResponse.self)
|
||||
.asObservable()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,271 @@
|
|||
//
|
||||
// ConfirmPopView.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
import URLNavigator
|
||||
|
||||
class ConfirmPopVC: DLCustomPopVC {
|
||||
|
||||
var disposeBag = DisposeBag()
|
||||
|
||||
// MARK: - Accessor
|
||||
public var confirmBlock: (() -> Void)?
|
||||
public var cancelBlock: (() -> Void)?
|
||||
|
||||
public var titleText: String? {
|
||||
didSet {
|
||||
titleLab.text = titleText
|
||||
}
|
||||
}
|
||||
public var contentText: String? {
|
||||
didSet {
|
||||
contentLab.text = contentText
|
||||
}
|
||||
}
|
||||
public var cancelText: String? {
|
||||
didSet {
|
||||
closeBtn.setTitle(cancelText, for: .normal)
|
||||
}
|
||||
}
|
||||
public var confirmText: String? {
|
||||
didSet {
|
||||
confirmBtn.setTitle(confirmText, for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupSubviews() {
|
||||
|
||||
contentBgView.addSubview(logoImgView)
|
||||
contentView.addSubview(headerBgImg)
|
||||
|
||||
if titleText != nil,
|
||||
titleText?.isEmpty == false {
|
||||
contentView.addSubview(titleLab)
|
||||
}
|
||||
|
||||
if contentText != nil,
|
||||
contentText?.isEmpty == false {
|
||||
contentView.addSubview(contentLab)
|
||||
}
|
||||
|
||||
if cancelText?.isEmpty == false || confirmText?.isEmpty == false {
|
||||
contentView.addSubview(stackView)
|
||||
}
|
||||
|
||||
closeBtn.isHidden = !(cancelText?.isEmpty == false)
|
||||
confirmBtn.isHidden = !(confirmText?.isEmpty == false)
|
||||
|
||||
}
|
||||
|
||||
private func setupLayout() {
|
||||
logoImgView.layoutChain
|
||||
.left(16)
|
||||
.top(-45)
|
||||
.width(109)
|
||||
.height(109)
|
||||
|
||||
headerBgImg.layoutChain
|
||||
.edges(excludingEdge: .bottom)
|
||||
.heightToWidth(100/315)
|
||||
|
||||
var tempView: UIView?
|
||||
if titleText != nil,
|
||||
titleText?.isEmpty == false {
|
||||
titleLab.layoutChain
|
||||
.top(53)
|
||||
.edgesHorzontal(20)
|
||||
tempView = titleLab
|
||||
}
|
||||
|
||||
if contentText != nil,
|
||||
contentText?.isEmpty == false {
|
||||
|
||||
if let tempView = tempView {
|
||||
contentLab.layoutChain
|
||||
.edgesHorzontal(20)
|
||||
.topToBottomOfView(tempView, offset: 20)
|
||||
} else {
|
||||
contentLab.layoutChain
|
||||
.top(53)
|
||||
.edgesHorzontal(20)
|
||||
}
|
||||
tempView = contentLab
|
||||
}
|
||||
|
||||
guard let tempView = tempView else { return }
|
||||
if cancelText?.isEmpty == false || confirmText?.isEmpty == false {
|
||||
stackView.layoutChain
|
||||
.topToBottomOfView(tempView, offset: 30)
|
||||
.left(20)
|
||||
.right(20)
|
||||
.bottom(15)
|
||||
|
||||
confirmBtn.layoutChain
|
||||
.height(50)
|
||||
|
||||
closeBtn.layoutChain
|
||||
.height(50)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UI
|
||||
private lazy var bgView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .black.withAlphaComponent(0.5)
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var popView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var popBgView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .white
|
||||
view.cornerRadius = 30
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var logoImgView: UIImageView = {
|
||||
let view = UIImageView(image: UIImage(named: "Popup/bell"))
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var headerBgImg: UIImageView = {
|
||||
let view = UIImageView(image: UIImage(named: "Popup/header_bg"))
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var titleLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 20, weight: .bold)
|
||||
label.textColor = UIColor(hexStr: "#1A1A1A")
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var contentLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
label.textColor = UIColor(hexStr: "#767676")
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var stackView: UIStackView = {
|
||||
let view = UIStackView(arrangedSubviews: [confirmBtn, closeBtn])
|
||||
view.spacing = 10
|
||||
view.axis = .vertical
|
||||
view.alignment = .fill
|
||||
view.distribution = .fill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var confirmBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setTitle("是", for: .normal)
|
||||
btn.setTitleColor(.white, for: .normal)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||||
btn.cornerRadius = 25
|
||||
btn.addTouchBlock { [weak self] _ in
|
||||
self?.dismiss(animated: true, completion: {
|
||||
self?.confirmBlock?()
|
||||
})
|
||||
}
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var closeBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setTitle("否", for: .normal)
|
||||
btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
btn.backgroundColor = .clear
|
||||
btn.addTouchBlock { [weak self] _ in
|
||||
self?.dismiss(animated: true, completion: {
|
||||
self?.cancelBlock?()
|
||||
})
|
||||
}
|
||||
return btn
|
||||
}()
|
||||
|
||||
// MARK: - Lifecycle
|
||||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
||||
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
||||
popStyle = .center
|
||||
centerCornerRadius = 30
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupSubviews()
|
||||
setupLayout()
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIViewController {
|
||||
/// 显示弹窗
|
||||
/// - Parameters:
|
||||
/// - title: 标题
|
||||
/// - message: 文本内容
|
||||
/// - confirmText: 确认按钮文案
|
||||
/// - confirmBlock: 确认按钮点击回调
|
||||
/// - cancelText: 取消按钮文案
|
||||
/// - cancelBlock: 取消按钮点击回调
|
||||
func showConfirmPop(showCloseBtn: Bool = true,
|
||||
title: String?=nil,
|
||||
message: String?=nil,
|
||||
confirmText: String?=nil,
|
||||
confirmBlock: (() -> Void)?=nil,
|
||||
cancelText: String?=nil,
|
||||
cancelBlock: (() -> Void)?=nil) {
|
||||
if title == nil && message == nil { return }
|
||||
let vc = ConfirmPopVC()
|
||||
vc.titleText = title
|
||||
vc.contentText = message
|
||||
vc.confirmText = confirmText
|
||||
vc.confirmBlock = confirmBlock
|
||||
vc.cancelText = cancelText
|
||||
vc.cancelBlock = cancelBlock
|
||||
vc.closeBtn.isHidden = !showCloseBtn
|
||||
vc.dimmingClick = showCloseBtn
|
||||
present(vc, animated: true)
|
||||
}
|
||||
|
||||
static func showConfirmPop(showCloseBtn: Bool = true,
|
||||
title: String?=nil,
|
||||
message: String?=nil,
|
||||
confirmText: String?=nil,
|
||||
confirmBlock: (() -> Void)?=nil,
|
||||
cancelText: String?=nil,
|
||||
cancelBlock: (() -> Void)?=nil) {
|
||||
if title == nil && message == nil { return }
|
||||
let vc = ConfirmPopVC()
|
||||
vc.titleText = title
|
||||
vc.contentText = message
|
||||
vc.confirmText = confirmText
|
||||
vc.confirmBlock = confirmBlock
|
||||
vc.cancelText = cancelText
|
||||
vc.cancelBlock = cancelBlock
|
||||
vc.closeBtn.isHidden = !showCloseBtn
|
||||
vc.dimmingClick = showCloseBtn
|
||||
UIViewController.topMost?.present(vc, animated: true)
|
||||
}
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ open class DLCustomPopVC: UIViewController {
|
|||
/// 自定义中心圆角大小,默认16,自动relative
|
||||
open var centerCornerRadius: CGFloat = 16
|
||||
/// 自定义中心内容边距,默认32,自动relative
|
||||
open var centerContentInset: CGFloat = 32
|
||||
open var centerContentInset: CGFloat = 30
|
||||
|
||||
// MARK: - Subviews
|
||||
/// 背景视图,默认可点击背景关闭
|
||||
|
|
@ -73,6 +73,13 @@ open class DLCustomPopVC: UIViewController {
|
|||
return view
|
||||
}()
|
||||
|
||||
/// 图标需要超出用这个view添加
|
||||
public lazy var contentBgView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
return view
|
||||
}()
|
||||
|
||||
/// 内容视图,高度须内部撑开
|
||||
public lazy var contentView: UIView = {
|
||||
let view = UIView()
|
||||
|
|
@ -100,19 +107,24 @@ open class DLCustomPopVC: UIViewController {
|
|||
view.backgroundColor = .clear
|
||||
|
||||
view.addSubview(backgroundView)
|
||||
view.addSubview(contentView)
|
||||
view.addSubview(contentBgView)
|
||||
contentBgView.addSubview(contentView)
|
||||
|
||||
backgroundView.layoutChain.edges()
|
||||
if popStyle == .bottom {
|
||||
contentView.layoutChain
|
||||
contentBgView.layoutChain
|
||||
.left()
|
||||
.right()
|
||||
.bottom()
|
||||
|
||||
contentView.layoutChain.edges()
|
||||
} else {
|
||||
contentView.layoutChain
|
||||
contentBgView.layoutChain
|
||||
.centerY()
|
||||
.left(centerContentInset)
|
||||
.right(centerContentInset)
|
||||
|
||||
contentView.layoutChain.edges()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||