diff --git a/QuickLocation.xcodeproj/project.pbxproj b/QuickLocation.xcodeproj/project.pbxproj index f8608c4..3152556 100644 --- a/QuickLocation.xcodeproj/project.pbxproj +++ b/QuickLocation.xcodeproj/project.pbxproj @@ -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 = ""; }; 30EFF3AC2FD7FF1400EB35D4 /* TextInputViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInputViewController.swift; sourceTree = ""; }; 30EFF3AF2FD8122E00EB35D4 /* GroupTagListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupTagListView.swift; sourceTree = ""; }; + 30EFF3B22FD8F1C200EB35D4 /* ReviewMemberListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewMemberListView.swift; sourceTree = ""; }; + 30EFF3B42FD8F1D000EB35D4 /* ReviewMemberListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewMemberListVC.swift; sourceTree = ""; }; + 30EFF3B62FD8F86200EB35D4 /* ReviewMemberListVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewMemberListVM.swift; sourceTree = ""; }; + 30EFF3B82FD8FC5200EB35D4 /* VerificationPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPopView.swift; sourceTree = ""; }; + 30EFF3BA2FD90D7600EB35D4 /* ConfirmPopVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmPopVC.swift; sourceTree = ""; }; + 30EFF3BD2FD958A100EB35D4 /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = ""; }; + 30EFF3BF2FD958AE00EB35D4 /* AccountVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountVC.swift; sourceTree = ""; }; 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 = ""; }; 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 = ""; @@ -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 = ""; }; + 30EFF3B12FD8F19E00EB35D4 /* ReviewMemberList */ = { + isa = PBXGroup; + children = ( + 30EFF3B42FD8F1D000EB35D4 /* ReviewMemberListVC.swift */, + 30EFF3B22FD8F1C200EB35D4 /* ReviewMemberListView.swift */, + 30EFF3B62FD8F86200EB35D4 /* ReviewMemberListVM.swift */, + ); + path = ReviewMemberList; + sourceTree = ""; + }; + 30EFF3BC2FD9585200EB35D4 /* Account */ = { + isa = PBXGroup; + children = ( + 30EFF3BF2FD958AE00EB35D4 /* AccountVC.swift */, + 30EFF3BD2FD958A100EB35D4 /* AccountView.swift */, + ); + path = Account; + sourceTree = ""; + }; 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 */, diff --git a/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate b/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate index 3562d90..829a354 100644 Binary files a/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate and b/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/QuickLocation/API/APIProvider.swift b/QuickLocation/API/APIProvider.swift index 7047f3f..09c10e5 100644 --- a/QuickLocation/API/APIProvider.swift +++ b/QuickLocation/API/APIProvider.swift @@ -114,6 +114,8 @@ public extension MoyaError { enum GatewayStatusCode: Int { // 请求成功 case success = 0 + // + case failure = -1 // 版本过低 case outdateVersion = 3 /** ============== 风控 ============== */ diff --git a/QuickLocation/Assets.xcassets/Common/button_bg_2.imageset/Contents.json b/QuickLocation/Assets.xcassets/Common/button_bg_2.imageset/Contents.json new file mode 100644 index 0000000..90b85d4 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Common/button_bg_2.imageset/Contents.json @@ -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 + } +} diff --git a/QuickLocation/Assets.xcassets/Common/button_bg_2.imageset/Rectangle 168@2x.png b/QuickLocation/Assets.xcassets/Common/button_bg_2.imageset/Rectangle 168@2x.png new file mode 100644 index 0000000..d1fdbd6 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Common/button_bg_2.imageset/Rectangle 168@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Common/button_bg_2.imageset/Rectangle 168@3x.png b/QuickLocation/Assets.xcassets/Common/button_bg_2.imageset/Rectangle 168@3x.png new file mode 100644 index 0000000..c1011e3 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Common/button_bg_2.imageset/Rectangle 168@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Popup/Contents.json b/QuickLocation/Assets.xcassets/Popup/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Popup/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/QuickLocation/Assets.xcassets/Popup/bell.imageset/Contents.json b/QuickLocation/Assets.xcassets/Popup/bell.imageset/Contents.json new file mode 100644 index 0000000..5f376a4 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Popup/bell.imageset/Contents.json @@ -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 + } +} diff --git a/QuickLocation/Assets.xcassets/Popup/bell.imageset/Group_1672@2x.png b/QuickLocation/Assets.xcassets/Popup/bell.imageset/Group_1672@2x.png new file mode 100644 index 0000000..b6cfb40 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Popup/bell.imageset/Group_1672@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Popup/bell.imageset/Group_1672@3x.png b/QuickLocation/Assets.xcassets/Popup/bell.imageset/Group_1672@3x.png new file mode 100644 index 0000000..cd6e636 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Popup/bell.imageset/Group_1672@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Popup/correct.imageset/Contents.json b/QuickLocation/Assets.xcassets/Popup/correct.imageset/Contents.json new file mode 100644 index 0000000..08d8973 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Popup/correct.imageset/Contents.json @@ -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 + } +} diff --git a/QuickLocation/Assets.xcassets/Popup/correct.imageset/correct@2x.png b/QuickLocation/Assets.xcassets/Popup/correct.imageset/correct@2x.png new file mode 100644 index 0000000..df907ed Binary files /dev/null and b/QuickLocation/Assets.xcassets/Popup/correct.imageset/correct@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Popup/correct.imageset/correct@3x.png b/QuickLocation/Assets.xcassets/Popup/correct.imageset/correct@3x.png new file mode 100644 index 0000000..7465974 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Popup/correct.imageset/correct@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Popup/error.imageset/Contents.json b/QuickLocation/Assets.xcassets/Popup/error.imageset/Contents.json new file mode 100644 index 0000000..8f8afd0 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Popup/error.imageset/Contents.json @@ -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 + } +} diff --git a/QuickLocation/Assets.xcassets/Popup/error.imageset/error@2x.png b/QuickLocation/Assets.xcassets/Popup/error.imageset/error@2x.png new file mode 100644 index 0000000..f67061d Binary files /dev/null and b/QuickLocation/Assets.xcassets/Popup/error.imageset/error@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Popup/error.imageset/error@3x.png b/QuickLocation/Assets.xcassets/Popup/error.imageset/error@3x.png new file mode 100644 index 0000000..29762f8 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Popup/error.imageset/error@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Popup/header_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/Popup/header_bg.imageset/Contents.json new file mode 100644 index 0000000..3c4bace --- /dev/null +++ b/QuickLocation/Assets.xcassets/Popup/header_bg.imageset/Contents.json @@ -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 + } +} diff --git a/QuickLocation/Assets.xcassets/Popup/header_bg.imageset/header_bg@2x.png b/QuickLocation/Assets.xcassets/Popup/header_bg.imageset/header_bg@2x.png new file mode 100644 index 0000000..2a5d271 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Popup/header_bg.imageset/header_bg@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Popup/header_bg.imageset/header_bg@3x.png b/QuickLocation/Assets.xcassets/Popup/header_bg.imageset/header_bg@3x.png new file mode 100644 index 0000000..0be4d09 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Popup/header_bg.imageset/header_bg@3x.png differ diff --git a/QuickLocation/Common/Constant.swift b/QuickLocation/Common/Constant.swift index 5305469..7c9a74d 100644 --- a/QuickLocation/Common/Constant.swift +++ b/QuickLocation/Common/Constant.swift @@ -24,6 +24,8 @@ extension DefaultsKeys { /// 通知常量 extension Notification.Name { + /// 刷新用户config + static let RefreshUserConfigNotification = Notification.Name("RefreshUserConfigNotification") /// 刷新用户圈子数据 static let RefreshGroupInfoNotification = Notification.Name("RefreshGroupInfoNotification") } diff --git a/QuickLocation/Core/Extension/UIView+Extension.swift b/QuickLocation/Core/Extension/UIView+Extension.swift index 456463d..13439ca 100644 --- a/QuickLocation/Core/Extension/UIView+Extension.swift +++ b/QuickLocation/Core/Extension/UIView+Extension.swift @@ -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 } diff --git a/QuickLocation/Manager/Account/UserConfigModel.swift b/QuickLocation/Manager/Account/UserConfigModel.swift index 7e58b77..2cf4323 100644 --- a/QuickLocation/Manager/Account/UserConfigModel.swift +++ b/QuickLocation/Manager/Account/UserConfigModel.swift @@ -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"] } } diff --git a/QuickLocation/Manager/App/ApiManager.swift b/QuickLocation/Manager/App/ApiManager.swift index c601a63..05d772c 100644 --- a/QuickLocation/Manager/App/ApiManager.swift +++ b/QuickLocation/Manager/App/ApiManager.swift @@ -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() } } } diff --git a/QuickLocation/Manager/App/RouterManager.swift b/QuickLocation/Manager/App/RouterManager.swift index 4522131..d3db21f 100644 --- a/QuickLocation/Manager/App/RouterManager.swift +++ b/QuickLocation/Manager/App/RouterManager.swift @@ -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 + } } } diff --git a/QuickLocation/Section/Group/GroupChat/GroupChatVC.swift b/QuickLocation/Section/Group/GroupChat/GroupChatVC.swift index bc6f58e..8cd9180 100644 --- a/QuickLocation/Section/Group/GroupChat/GroupChatVC.swift +++ b/QuickLocation/Section/Group/GroupChat/GroupChatVC.swift @@ -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 } } diff --git a/QuickLocation/Section/Group/GroupChat/GroupChatView.swift b/QuickLocation/Section/Group/GroupChat/GroupChatView.swift index 13125da..e7bdda8 100644 --- a/QuickLocation/Section/Group/GroupChat/GroupChatView.swift +++ b/QuickLocation/Section/Group/GroupChat/GroupChatView.swift @@ -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 { diff --git a/QuickLocation/Section/Group/GroupChat/GroupChatViewModel.swift b/QuickLocation/Section/Group/GroupChat/GroupChatViewModel.swift index b2cee24..9b316d9 100644 --- a/QuickLocation/Section/Group/GroupChat/GroupChatViewModel.swift +++ b/QuickLocation/Section/Group/GroupChat/GroupChatViewModel.swift @@ -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 @@ -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 + } } } diff --git a/QuickLocation/Section/Group/GroupIMService.swift b/QuickLocation/Section/Group/GroupIMService.swift index 6313bdf..0d18eb8 100644 --- a/QuickLocation/Section/Group/GroupIMService.swift +++ b/QuickLocation/Section/Group/GroupIMService.swift @@ -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 diff --git a/QuickLocation/Section/Group/GroupSetting/GroupSettingVC.swift b/QuickLocation/Section/Group/GroupSetting/GroupSettingVC.swift index 537d7c7..4863bf9 100644 --- a/QuickLocation/Section/Group/GroupSetting/GroupSettingVC.swift +++ b/QuickLocation/Section/Group/GroupSetting/GroupSettingVC.swift @@ -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) { diff --git a/QuickLocation/Section/Group/GroupViewController.swift b/QuickLocation/Section/Group/GroupViewController.swift index 3a79189..685534f 100644 --- a/QuickLocation/Section/Group/GroupViewController.swift +++ b/QuickLocation/Section/Group/GroupViewController.swift @@ -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) } } diff --git a/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVC.swift b/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVC.swift new file mode 100644 index 0000000..4a2be41 --- /dev/null +++ b/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVC.swift @@ -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 = { + return RxTableViewSectionedReloadDataSource( + 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") + } +} diff --git a/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVM.swift b/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVM.swift new file mode 100644 index 0000000..09a5856 --- /dev/null +++ b/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVM.swift @@ -0,0 +1,35 @@ +// +// ReviewMemberListVM.swift +// QuickLocation +// +// Created by 八条 on 2026/6/10. +// + +import RxSwift +import RxDataSources +import ObjectMapper + +typealias ReviewMemberListSectionModel = SectionModel + +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() + ) + } +} diff --git a/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListView.swift b/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListView.swift new file mode 100644 index 0000000..d920782 --- /dev/null +++ b/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListView.swift @@ -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 + } +} diff --git a/QuickLocation/Section/Group/VerificationPopView.swift b/QuickLocation/Section/Group/VerificationPopView.swift new file mode 100644 index 0000000..5ff6c12 --- /dev/null +++ b/QuickLocation/Section/Group/VerificationPopView.swift @@ -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() + + } + +} diff --git a/QuickLocation/Section/Home/HomeViewController.swift b/QuickLocation/Section/Home/HomeViewController.swift index a30544d..1742390 100644 --- a/QuickLocation/Section/Home/HomeViewController.swift +++ b/QuickLocation/Section/Home/HomeViewController.swift @@ -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 } diff --git a/QuickLocation/Section/Launch/LaunchViewController.swift b/QuickLocation/Section/Launch/LaunchViewController.swift index 1345182..4cf20ff 100644 --- a/QuickLocation/Section/Launch/LaunchViewController.swift +++ b/QuickLocation/Section/Launch/LaunchViewController.swift @@ -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) diff --git a/QuickLocation/Section/Login/LoginView.swift b/QuickLocation/Section/Login/LoginView.swift index b98f635..bb76173 100644 --- a/QuickLocation/Section/Login/LoginView.swift +++ b/QuickLocation/Section/Login/LoginView.swift @@ -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 }() diff --git a/QuickLocation/Section/Login/LoginViewModel.swift b/QuickLocation/Section/Login/LoginViewModel.swift index 57937d3..49d83a0 100644 --- a/QuickLocation/Section/Login/LoginViewModel.swift +++ b/QuickLocation/Section/Login/LoginViewModel.swift @@ -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) } diff --git a/QuickLocation/Section/Mine/Account/AccountVC.swift b/QuickLocation/Section/Mine/Account/AccountVC.swift new file mode 100644 index 0000000..0c0d3eb --- /dev/null +++ b/QuickLocation/Section/Mine/Account/AccountVC.swift @@ -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) + } +} diff --git a/QuickLocation/Section/Mine/Account/AccountView.swift b/QuickLocation/Section/Mine/Account/AccountView.swift new file mode 100644 index 0000000..a955823 --- /dev/null +++ b/QuickLocation/Section/Mine/Account/AccountView.swift @@ -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") + } + +} diff --git a/QuickLocation/Section/Mine/MineView.swift b/QuickLocation/Section/Mine/MineView.swift index 13de4d5..df57f9d 100644 --- a/QuickLocation/Section/Mine/MineView.swift +++ b/QuickLocation/Section/Mine/MineView.swift @@ -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 }() diff --git a/QuickLocation/Section/Mine/MineViewController.swift b/QuickLocation/Section/Mine/MineViewController.swift index a1b5118..ae822e7 100644 --- a/QuickLocation/Section/Mine/MineViewController.swift +++ b/QuickLocation/Section/Mine/MineViewController.swift @@ -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) } } diff --git a/QuickLocation/Section/Mine/MineViewModel.swift b/QuickLocation/Section/Mine/MineViewModel.swift index f6e746a..e1a2a73 100644 --- a/QuickLocation/Section/Mine/MineViewModel.swift +++ b/QuickLocation/Section/Mine/MineViewModel.swift @@ -35,7 +35,12 @@ class MineViewModel { lazy var cellAction: Action = { this in return Action { model in - + switch model.title { + case "账户与安全": + AppRouter.push(Route.account) + default: + break + } return .empty() } }(self) diff --git a/QuickLocation/Service/GroupService.swift b/QuickLocation/Service/GroupService.swift index 01a1019..0164900 100644 --- a/QuickLocation/Service/GroupService.swift +++ b/QuickLocation/Service/GroupService.swift @@ -95,4 +95,24 @@ struct GroupService { .map(GroupInfoResponse.self) .asObservable() } + + /// 审核列表 + /// - Parameters: + /// - requestData:group_key + static func reviewlist(requestData: [String: Any]) -> Observable { + 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 { + let api = GroupAPI.operate(opType: "leave", requestData: requestData).multiTarget + return APIProvider.request(token: api) + .map(GroupInfoResponse.self) + .asObservable() + } } diff --git a/QuickLocation/UIKit/Pop/ConfirmPopVC.swift b/QuickLocation/UIKit/Pop/ConfirmPopVC.swift new file mode 100644 index 0000000..7011231 --- /dev/null +++ b/QuickLocation/UIKit/Pop/ConfirmPopVC.swift @@ -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) + } +} diff --git a/QuickLocation/UIKit/Pop/DLCustomPopVC.swift b/QuickLocation/UIKit/Pop/DLCustomPopVC.swift index daf0c2e..91c5eae 100644 --- a/QuickLocation/UIKit/Pop/DLCustomPopVC.swift +++ b/QuickLocation/UIKit/Pop/DLCustomPopVC.swift @@ -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() } }