diff --git a/QuickLocation.xcodeproj/project.pbxproj b/QuickLocation.xcodeproj/project.pbxproj index 557f910..7754c20 100644 --- a/QuickLocation.xcodeproj/project.pbxproj +++ b/QuickLocation.xcodeproj/project.pbxproj @@ -182,6 +182,12 @@ 30BAB8532FCD337C00C33B5C /* GroupService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8522FCD337C00C33B5C /* GroupService.swift */; }; 30BAB8632FCD716C00C33B5C /* JoinGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8622FCD716C00C33B5C /* JoinGroupVC.swift */; }; 30BAB8652FCD718A00C33B5C /* JoinGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8642FCD718A00C33B5C /* JoinGroupView.swift */; }; + 30C4C0162FDB91B8009215C1 /* CheckPermissionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C0152FDB91B8009215C1 /* CheckPermissionVC.swift */; }; + 30C4C0192FDBF094009215C1 /* RemoveMemberVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C0182FDBF094009215C1 /* RemoveMemberVC.swift */; }; + 30C4C01B2FDBF09D009215C1 /* RemoveMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C01A2FDBF09D009215C1 /* RemoveMemberView.swift */; }; + 30C4C01D2FDBF557009215C1 /* RemoveMemberViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C01C2FDBF557009215C1 /* RemoveMemberViewModel.swift */; }; + 30C4C0202FDC0EC5009215C1 /* GroupInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C01F2FDC0EC5009215C1 /* GroupInfoView.swift */; }; + 30C4C0222FDC0ED3009215C1 /* GroupInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C0212FDC0ED3009215C1 /* GroupInfoVC.swift */; }; 30DC18522FD009CD0041DCD1 /* VipExpenseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */; }; 30DC18542FD00C4A0041DCD1 /* VipRechargeVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */; }; 30DC185A2FD11E7A0041DCD1 /* WebOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18562FD11E7A0041DCD1 /* WebOperations.swift */; }; @@ -408,6 +414,12 @@ 30BAB8622FCD716C00C33B5C /* JoinGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinGroupVC.swift; sourceTree = ""; }; 30BAB8642FCD718A00C33B5C /* JoinGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinGroupView.swift; sourceTree = ""; }; 30C4C0112FDABC8C009215C1 /* QuickLocation.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QuickLocation.entitlements; sourceTree = ""; }; + 30C4C0152FDB91B8009215C1 /* CheckPermissionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckPermissionVC.swift; sourceTree = ""; }; + 30C4C0182FDBF094009215C1 /* RemoveMemberVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveMemberVC.swift; sourceTree = ""; }; + 30C4C01A2FDBF09D009215C1 /* RemoveMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveMemberView.swift; sourceTree = ""; }; + 30C4C01C2FDBF557009215C1 /* RemoveMemberViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveMemberViewModel.swift; sourceTree = ""; }; + 30C4C01F2FDC0EC5009215C1 /* GroupInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInfoView.swift; sourceTree = ""; }; + 30C4C0212FDC0ED3009215C1 /* GroupInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInfoVC.swift; sourceTree = ""; }; 30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipExpenseModel.swift; sourceTree = ""; }; 30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipRechargeVM.swift; sourceTree = ""; }; 30DC18552FD11E7A0041DCD1 /* NavigationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTitleView.swift; sourceTree = ""; }; @@ -820,9 +832,11 @@ 305A76252FCA8C7000227D26 /* GroupViewModel.swift */, 305A76232FCA8C7000227D26 /* GroupView.swift */, 30EFF3B82FD8FC5200EB35D4 /* VerificationPopView.swift */, + 30C4C01E2FDC0EA6009215C1 /* GroupInfo */, 307073E42FD18A20004C37CC /* GroupChat */, 30EFF3A22FD7C58400EB35D4 /* GroupSetting */, 30EFF3B12FD8F19E00EB35D4 /* ReviewMemberList */, + 30C4C0172FDBF066009215C1 /* RemoveMember */, 3062E8B82FCEAC5600CEF511 /* CreateGroup */, 30BAB8612FCD714700C33B5C /* Join */, 30BAB84B2FCD2FA400C33B5C /* InviteJoin */, @@ -881,6 +895,7 @@ 30EFF3BC2FD9585200EB35D4 /* Account */, 30EFF3D42FDA8EF200EB35D4 /* EmergencyContact */, 30EFF3E32FDAA92200EB35D4 /* PrivacyPolicy */, + 30C4C0122FDB9178009215C1 /* CheckPermission */, ); path = Mine; sourceTree = ""; @@ -888,14 +903,14 @@ 305A763A2FCA8C7000227D26 /* Section */ = { isa = PBXGroup; children = ( - 305A76262FCA8C7000227D26 /* Group */, 305A762A2FCA8C7000227D26 /* Home */, - 305A762C2FCA8C7000227D26 /* Launch */, - 305A76312FCA8C7000227D26 /* Login */, 305A76352FCA8C7000227D26 /* Map */, + 305A76262FCA8C7000227D26 /* Group */, 305A76392FCA8C7000227D26 /* Mine */, 305A798E2FCAC5F600227D26 /* InviteMember */, 3062E8C52FCFD01000CEF511 /* VipRecharge */, + 305A76312FCA8C7000227D26 /* Login */, + 305A762C2FCA8C7000227D26 /* Launch */, 3062E8B32FCE6BA400CEF511 /* Scan */, 30DC18592FD11E7A0041DCD1 /* Web */, 30EFF3AD2FD7FF1400EB35D4 /* TextInput */, @@ -1132,6 +1147,33 @@ path = Join; sourceTree = ""; }; + 30C4C0122FDB9178009215C1 /* CheckPermission */ = { + isa = PBXGroup; + children = ( + 30C4C0152FDB91B8009215C1 /* CheckPermissionVC.swift */, + ); + path = CheckPermission; + sourceTree = ""; + }; + 30C4C0172FDBF066009215C1 /* RemoveMember */ = { + isa = PBXGroup; + children = ( + 30C4C0182FDBF094009215C1 /* RemoveMemberVC.swift */, + 30C4C01C2FDBF557009215C1 /* RemoveMemberViewModel.swift */, + 30C4C01A2FDBF09D009215C1 /* RemoveMemberView.swift */, + ); + path = RemoveMember; + sourceTree = ""; + }; + 30C4C01E2FDC0EA6009215C1 /* GroupInfo */ = { + isa = PBXGroup; + children = ( + 30C4C0212FDC0ED3009215C1 /* GroupInfoVC.swift */, + 30C4C01F2FDC0EC5009215C1 /* GroupInfoView.swift */, + ); + path = GroupInfo; + sourceTree = ""; + }; 30DC18582FD11E7A0041DCD1 /* Controller */ = { isa = PBXGroup; children = ( @@ -1173,8 +1215,8 @@ isa = PBXGroup; children = ( 30EFF3B42FD8F1D000EB35D4 /* ReviewMemberListVC.swift */, - 30EFF3B22FD8F1C200EB35D4 /* ReviewMemberListView.swift */, 30EFF3B62FD8F86200EB35D4 /* ReviewMemberListVM.swift */, + 30EFF3B22FD8F1C200EB35D4 /* ReviewMemberListView.swift */, ); path = ReviewMemberList; sourceTree = ""; @@ -1412,6 +1454,7 @@ 305A768D2FCA8C7000227D26 /* AppNetworkConfig.swift in Sources */, 305A768E2FCA8C7000227D26 /* SignPlugin.swift in Sources */, 305A768F2FCA8C7000227D26 /* SystemAPI.swift in Sources */, + 30C4C0222FDC0ED3009215C1 /* GroupInfoVC.swift in Sources */, 305A76902FCA8C7000227D26 /* UserAPI.swift in Sources */, 30EFF3DE2FDA982C00EB35D4 /* EmergencyContactAddVC.swift in Sources */, 305A76912FCA8C7000227D26 /* Constant.swift in Sources */, @@ -1422,6 +1465,7 @@ 305A76942FCA8C7000227D26 /* UploadImageCell.swift in Sources */, 305A76952FCA8C7000227D26 /* CornerRadiusCell.swift in Sources */, 305A76962FCA8C7000227D26 /* CornerRadiusFooterView.swift in Sources */, + 30C4C0162FDB91B8009215C1 /* CheckPermissionVC.swift in Sources */, 305A76972FCA8C7000227D26 /* CornerRadiusHeaderView.swift in Sources */, 305A76982FCA8C7000227D26 /* ImagePicker.swift in Sources */, 305A76992FCA8C7000227D26 /* ImagePickerPopup.swift in Sources */, @@ -1478,6 +1522,7 @@ 305A76BD2FCA8C7000227D26 /* PaginationModel.swift in Sources */, 305A76BE2FCA8C7000227D26 /* ResponseModel.swift in Sources */, 30EFF3C02FD958AE00EB35D4 /* AccountVC.swift in Sources */, + 30C4C0202FDC0EC5009215C1 /* GroupInfoView.swift in Sources */, 305A76BF2FCA8C7000227D26 /* ListService.swift in Sources */, 305A76C02FCA8C7000227D26 /* BaseNavigationController.swift in Sources */, 305A76C12FCA8C7000227D26 /* BaseViewController.swift in Sources */, @@ -1489,6 +1534,7 @@ 305A76C32FCA8C7000227D26 /* MainTabBarController.swift in Sources */, 30EFF3CA2FDA575600EB35D4 /* CancellationPopView.swift in Sources */, 305A76C42FCA8C7000227D26 /* QuickLocationTabBar.swift in Sources */, + 30C4C01D2FDBF557009215C1 /* RemoveMemberViewModel.swift in Sources */, 305A76C52FCA8C7000227D26 /* Account.swift in Sources */, 305A76C62FCA8C7000227D26 /* AppContextManager.swift in Sources */, 305A76C72FCA8C7000227D26 /* UserConfigModel.swift in Sources */, @@ -1556,6 +1602,7 @@ 305A76F02FCA8C7000227D26 /* MineViewModel.swift in Sources */, 305A76F12FCA8C7000227D26 /* SystemService.swift in Sources */, 30DC18522FD009CD0041DCD1 /* VipExpenseModel.swift in Sources */, + 30C4C01B2FDBF09D009215C1 /* RemoveMemberView.swift in Sources */, 307073E12FD15F50004C37CC /* GroupIMService.swift in Sources */, 305A76F22FCA8C7000227D26 /* UserService.swift in Sources */, 305A76F32FCA8C7000227D26 /* AutoLayout+NSLayoutConstraint.swift in Sources */, @@ -1575,6 +1622,7 @@ 305A76FD2FCA8C7000227D26 /* EmptyDataSetSource.swift in Sources */, 30EFF3CD2FDA668A00EB35D4 /* MyProfileView.swift in Sources */, 305A76FE2FCA8C7000227D26 /* EmptyDataSetView.swift in Sources */, + 30C4C0192FDBF094009215C1 /* RemoveMemberVC.swift in Sources */, 305A76FF2FCA8C7000227D26 /* EmptyDataSetView+Extension.swift in Sources */, 305A77002FCA8C7000227D26 /* RefreshStyle.swift in Sources */, 305A77012FCA8C7000227D26 /* DLHUD.swift in Sources */, @@ -1643,7 +1691,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 434CGNSJ28; + DEVELOPMENT_TEAM = 7LP48T8ZJE; ENABLE_USER_SCRIPT_SANDBOXING = "$(inherited)"; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = QuickLocation/Info.plist; @@ -1664,7 +1712,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = cn.zuom8.jisulocation; + PRODUCT_BUNDLE_IDENTIFIER = cn.zuom8.jisuloca; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; STRING_CATALOG_GENERATE_SYMBOLS = YES; @@ -1692,7 +1740,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 434CGNSJ28; + DEVELOPMENT_TEAM = 7LP48T8ZJE; ENABLE_USER_SCRIPT_SANDBOXING = "$(inherited)"; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = QuickLocation/Info.plist; @@ -1713,7 +1761,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = cn.zuom8.jisulocation; + PRODUCT_BUNDLE_IDENTIFIER = cn.zuom8.jisuloca; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; STRING_CATALOG_GENERATE_SYMBOLS = YES; diff --git a/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate b/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate index 8c32bec..f3be8e9 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/Assets.xcassets/CheckPermission/Contents.json b/QuickLocation/Assets.xcassets/CheckPermission/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/QuickLocation/Assets.xcassets/CheckPermission/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/QuickLocation/Assets.xcassets/CheckPermission/permission.imageset/Contents.json b/QuickLocation/Assets.xcassets/CheckPermission/permission.imageset/Contents.json new file mode 100644 index 0000000..4577d7a --- /dev/null +++ b/QuickLocation/Assets.xcassets/CheckPermission/permission.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "permission@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "permission@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/CheckPermission/permission.imageset/permission@2x.png b/QuickLocation/Assets.xcassets/CheckPermission/permission.imageset/permission@2x.png new file mode 100644 index 0000000..da063ff Binary files /dev/null and b/QuickLocation/Assets.xcassets/CheckPermission/permission.imageset/permission@2x.png differ diff --git a/QuickLocation/Assets.xcassets/CheckPermission/permission.imageset/permission@3x.png b/QuickLocation/Assets.xcassets/CheckPermission/permission.imageset/permission@3x.png new file mode 100644 index 0000000..ea8fffd Binary files /dev/null and b/QuickLocation/Assets.xcassets/CheckPermission/permission.imageset/permission@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Common/checkbox_20x20.imageset/Contents.json b/QuickLocation/Assets.xcassets/Common/checkbox_20x20.imageset/Contents.json new file mode 100644 index 0000000..6be7d01 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Common/checkbox_20x20.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Rectangle_281@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Rectangle_281@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Common/checkbox_20x20.imageset/Rectangle_281@2x.png b/QuickLocation/Assets.xcassets/Common/checkbox_20x20.imageset/Rectangle_281@2x.png new file mode 100644 index 0000000..697af77 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Common/checkbox_20x20.imageset/Rectangle_281@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Common/checkbox_20x20.imageset/Rectangle_281@3x.png b/QuickLocation/Assets.xcassets/Common/checkbox_20x20.imageset/Rectangle_281@3x.png new file mode 100644 index 0000000..1899b81 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Common/checkbox_20x20.imageset/Rectangle_281@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Common/delete_check.imageset/Contents.json b/QuickLocation/Assets.xcassets/Common/delete_check.imageset/Contents.json new file mode 100644 index 0000000..6a4d508 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Common/delete_check.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Vector@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Vector@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Common/delete_check.imageset/Vector@2x.png b/QuickLocation/Assets.xcassets/Common/delete_check.imageset/Vector@2x.png new file mode 100644 index 0000000..27dc563 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Common/delete_check.imageset/Vector@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Common/delete_check.imageset/Vector@3x.png b/QuickLocation/Assets.xcassets/Common/delete_check.imageset/Vector@3x.png new file mode 100644 index 0000000..184ebde Binary files /dev/null and b/QuickLocation/Assets.xcassets/Common/delete_check.imageset/Vector@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Common/owner.imageset/Contents.json b/QuickLocation/Assets.xcassets/Common/owner.imageset/Contents.json new file mode 100644 index 0000000..eaf66f6 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Common/owner.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_614@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_614@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Common/owner.imageset/Group_614@2x.png b/QuickLocation/Assets.xcassets/Common/owner.imageset/Group_614@2x.png new file mode 100644 index 0000000..dd5838c Binary files /dev/null and b/QuickLocation/Assets.xcassets/Common/owner.imageset/Group_614@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Common/owner.imageset/Group_614@3x.png b/QuickLocation/Assets.xcassets/Common/owner.imageset/Group_614@3x.png new file mode 100644 index 0000000..80e4883 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Common/owner.imageset/Group_614@3x.png differ diff --git a/QuickLocation/Main/BaseViewController/BaseViewController.swift b/QuickLocation/Main/BaseViewController/BaseViewController.swift index 9e86108..ce8d4d7 100644 --- a/QuickLocation/Main/BaseViewController/BaseViewController.swift +++ b/QuickLocation/Main/BaseViewController/BaseViewController.swift @@ -33,8 +33,8 @@ class BaseViewController: UIViewController { /// 导航栏标题 var navTitle: String = "" { didSet { -// backButton.setTitle(" \(navTitle)", for: .normal) - navigationItem.title = navTitle + backButton.setTitle(" \(navTitle)", for: .normal) +// navigationItem.title = navTitle } } diff --git a/QuickLocation/Manager/Account/AppContextManager.swift b/QuickLocation/Manager/Account/AppContextManager.swift index 91a92ac..b8e156e 100644 --- a/QuickLocation/Manager/Account/AppContextManager.swift +++ b/QuickLocation/Manager/Account/AppContextManager.swift @@ -58,6 +58,10 @@ class AppContextManager: NSObject { var sex: Int { account?.sex ?? -1 } + /// VIP + var vip: Int { + account?.vip ?? 1 + } @discardableResult public func saveAccount(_ account: UserConfigModel) -> Bool { diff --git a/QuickLocation/Manager/Account/UserConfigModel.swift b/QuickLocation/Manager/Account/UserConfigModel.swift index b91af9f..77db9ea 100644 --- a/QuickLocation/Manager/Account/UserConfigModel.swift +++ b/QuickLocation/Manager/Account/UserConfigModel.swift @@ -58,6 +58,9 @@ struct UserConfigModel: Mappable { } struct SystemConfigModel: Mappable { + /// 聊天界面的警告说明 + var chatWarning: String = "" + /// 圈子里的banner var groupBannerList: [String] = [] @@ -66,6 +69,7 @@ struct SystemConfigModel: Mappable { } mutating func mapping(map: Map) { + chatWarning <- map["client.chatwarning", nested: false] groupBannerList <- map["client.team.ad", nested: false] } } diff --git a/QuickLocation/Manager/App/RouterManager.swift b/QuickLocation/Manager/App/RouterManager.swift index 0b2cde4..a919f2c 100644 --- a/QuickLocation/Manager/App/RouterManager.swift +++ b/QuickLocation/Manager/App/RouterManager.swift @@ -31,8 +31,14 @@ enum Route: String { case vipRights = "vipRights" /// 群聊 case groupChat = "groupChat" + /// 圈子资料 + case groupInfo = "groupInfo" /// 圈子设置 case groupSetting = "groupSetting" + /// 待审核列表 + case reviewMemberList = "reviewMemberList" + /// 移除圈子成员 + case removeMember = "removeMember" /// 账号与安全 case account = "account" /// 更换手机号 @@ -45,6 +51,8 @@ enum Route: String { case emergencyContactAdd = "emergencyContactAdd" /// 隐私与协议 case privacyPolicy = "privacyPolicy" + /// 权限检测 + case checkPermission = "checkPermission" } extension Route: RouterTarget { @@ -181,12 +189,30 @@ extension AppRouter: AppRouterProtocol { return GroupChatVC(groupId: groupId) } + // MARK: - 圈子资料 + AppRouter.register(Route.groupInfo) { url, parameters in + let vc = GroupInfoVC(groupInfo: parameters["groupInfo"].safeDictionary as! [String : Any]) + return vc + } + // MARK: - 圈子设置 AppRouter.register(Route.groupSetting) { url, parameters in let groupId = parameters["groupId"].safeString return GroupSettingVC(groupId: groupId) } + // MARK: - 待审核列表 + AppRouter.register(Route.reviewMemberList) { url, parameters in + let groupId = parameters["groupId"].safeString + return ReviewMemberListVC(groupId: groupId) + } + + // MARK: - 移除圈子成员 + AppRouter.register(Route.removeMember) { url, parameters in + let groupId = parameters["groupId"].safeString + return RemoveMemberVC(groupId: groupId) + } + // MARK: - 账号与安全 AppRouter.register(Route.account) { url, parameters in let vc = AccountVC() @@ -226,6 +252,11 @@ extension AppRouter: AppRouterProtocol { AppRouter.register(Route.privacyPolicy) { url, parameters in PrivacyPolicyVC() } + + // MARK: - 权限检测 + AppRouter.register(Route.checkPermission) { url, parameters in + CheckPermissionVC() + } } } diff --git a/QuickLocation/Model/GroupModel.swift b/QuickLocation/Model/GroupModel.swift index 39173ce..6d9f0be 100644 --- a/QuickLocation/Model/GroupModel.swift +++ b/QuickLocation/Model/GroupModel.swift @@ -54,6 +54,8 @@ struct GroupInfoResponse: BaseModelProtocol { var model: GroupInfoModel? // 圈子成员列表 var list: [GroupMemberModel] = [] + // 待审核数量 + var reviewCount: Int = 0 init?(map: Map) {} @@ -62,6 +64,24 @@ struct GroupInfoResponse: BaseModelProtocol { message <- map["msg"] model <- map["data.attr"] list <- map["data.employee"] + reviewCount <- map["data.reviewCount"] + } +} + +struct ReviewMemberListResponse: BaseModelProtocol { + // 状态码 + var code: String? + // 消息 + var message: String? + /// 待审核列表 + var list: [GroupMemberModel] = [] + + init?(map: Map) {} + + mutating func mapping(map: Map) { + code <- map["code"] + message <- map["msg"] + list <- map["data"] } } @@ -115,6 +135,8 @@ struct GroupInfoModel: Mappable, Equatable { var description: String = "" /// 会员等级 var level: String = "" + /// 邀请码 + var share_code: String = "" init?(map: Map) { @@ -129,6 +151,7 @@ struct GroupInfoModel: Mappable, Equatable { level <- map["level"] review <- map["review"] description <- map["description"] + share_code <- map["share_code"] people_no <- (map["people_no"], kStrTransformInt) } } diff --git a/QuickLocation/QuickLocation.entitlements b/QuickLocation/QuickLocation.entitlements index 9e7e98c..0c67376 100644 --- a/QuickLocation/QuickLocation.entitlements +++ b/QuickLocation/QuickLocation.entitlements @@ -1,10 +1,5 @@ - - com.apple.developer.associated-domains - - applinks:smartdrive.zuom8.cn - - + diff --git a/QuickLocation/Section/Group/GroupChat/GroupChatVC.swift b/QuickLocation/Section/Group/GroupChat/GroupChatVC.swift index d27d762..01f05c7 100644 --- a/QuickLocation/Section/Group/GroupChat/GroupChatVC.swift +++ b/QuickLocation/Section/Group/GroupChat/GroupChatVC.swift @@ -46,16 +46,26 @@ final class GroupChatVC: BaseViewController { setupMessageListener() setupVoiceRecording() setupPanelDismiss() - setupKeyboard() + // 并行加载:业务接口 + IM SDK 互不依赖,同时发起 requestGroupInfoByKey() viewModel.loadMessages() + + // 处理系统配置 + guard let config = AppContextManager.shared.systemConfig else { return } + rootView.chatWarningView.isHidden = config.chatWarning.isEmpty + rootView.chatWarningLab.text = config.chatWarning } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + setupKeyboard() IQKeyboardManager.shared.isEnabled = false IQKeyboardManager.shared.resignOnTouchOutside = false + + // 非圈主 会员权益拦截 + guard viewModel.groupId.contains(AppContextManager.shared.userId) == false else { return } + rootView.disableIMView.isHidden = AppContextManager.shared.vip > 1 } override func viewWillDisappear(_ animated: Bool) { @@ -64,6 +74,11 @@ final class GroupChatVC: BaseViewController { IQKeyboardManager.shared.isEnabled = true IQKeyboardManager.shared.resignOnTouchOutside = true } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + NotificationCenter.default.removeObserver(self) + } // MARK: - Keyboard private func setupKeyboard() { @@ -71,6 +86,8 @@ final class GroupChatVC: BaseViewController { NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification) .subscribe(onNext: { [weak self] noti in guard let self = self, + // 只在 VC 本身可见时响应,避免导航栈下层 VC 被全局通知唤醒 + self.isViewLoaded && self.view.window != nil, let userInfo = noti.userInfo, let frame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } @@ -165,6 +182,10 @@ final class GroupChatVC: BaseViewController { }) .disposed(by: disposeBag) + rootView.reviewBtn.rx.tap.subscribe(onNext: { _ in + AppRouter.push(Route.reviewMemberList, userInfo: ["groupId": self.viewModel.groupId]) + }).disposed(by: disposeBag) + // 语音按钮 rootView.voiceBtn.rx.tap.subscribe(onNext: { [weak self] _ in guard let self = self else { return } @@ -410,10 +431,10 @@ final class GroupChatVC: BaseViewController { GroupService.groupInfoByKey(viewModel.groupId).subscribe { response in guard let model = response.model else { return } self.viewModel.memberList = response.list - self.viewModel.buildAvatarCache() self.rootView.groupNameLabel.text = model.name self.rootView.groupAvatarView.image = model.groupIcon self.rootView.reviewBtn.isHidden = !model.is_owner + self.rootView.reviewDotView.isHidden = response.reviewCount == 0 }.disposed(by: disposeBag) } @@ -517,6 +538,7 @@ extension GroupChatVC: PhotoPickerControllerDelegate { let localMsg = ChatMessage( id: localId, isSelf: true, + senderId: AppContextManager.shared.userId, avatar: viewModel.getUserAvatar(id: AppContextManager.shared.userId), senderName: AppContextManager.shared.name, content: "", diff --git a/QuickLocation/Section/Group/GroupChat/GroupChatView.swift b/QuickLocation/Section/Group/GroupChat/GroupChatView.swift index e7bdda8..9c597bf 100644 --- a/QuickLocation/Section/Group/GroupChat/GroupChatView.swift +++ b/QuickLocation/Section/Group/GroupChat/GroupChatView.swift @@ -21,6 +21,7 @@ enum VoiceRecordState { struct ChatMessage { let id: String let isSelf: Bool + let senderId: String let avatar: UIImage let senderName: String let content: String @@ -78,9 +79,12 @@ class GroupChatView: UIView { rightIconsView.addSubview(reviewBtn) rightIconsView.addSubview(memberBtn) rightIconsView.addSubview(settingBtn) + rightIconsView.addSubview(reviewDotView) addSubview(tableView) + addSubview(chatWarningView) addSubview(bottomBar) + addSubview(disableIMView) bottomBar.addSubview(bottomBarCornerView) addSubview(voiceRecordView) addSubview(emojiPanelView) @@ -127,6 +131,12 @@ class GroupChatView: UIView { .left().centerY() .width(24).height(24) + reviewDotView.layoutChain + .topToView(reviewBtn, offset: -2) + .leftToRightOfView(reviewBtn, offset: -6) + .width(8) + .height(8) + memberBtn.layoutChain .leftToRightOfView(reviewBtn, offset: 20) .centerY() @@ -143,11 +153,20 @@ class GroupChatView: UIView { .edgesHorzontal() .bottomToTopOfView(bottomBar) + chatWarningView.layoutChain + .topToBottomOfView(navBarView) + .edgesHorzontal() + bottomBar.layoutChain .edgesHorzontal(15) .height(50) .bottom(kSafeBottomMargin + 20) + disableIMView.layoutChain + .edgesHorzontal(15) + .height(50) + .bottom(kSafeBottomMargin + 20) + bottomBarBottomConstraint = bottomBar.jh_constraint(.bottom, toAttribute: .bottom, otherView: bottomBar.superview, relation: .equal) bottomBarCornerView.layoutChain.edges() @@ -253,6 +272,14 @@ class GroupChatView: UIView { btn.setImage(UIImage(named: "IM/review"), for: .normal) return btn }() + + lazy var reviewDotView: UIView = { + let view = UIView() + view.backgroundColor = .red + view.cornerRadius = 4 + view.isHidden = true + return view + }() lazy var memberBtn: UIButton = { let btn = UIButton(type: .custom) @@ -265,7 +292,48 @@ class GroupChatView: UIView { btn.setImage(UIImage(named: "IM/setting"), for: .normal) return btn }() - + + // MARK: - chatwarning + lazy var chatWarningView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.5) + view.isHidden = true + + view.addSubview(closeBtn) + closeBtn.layoutChain + .right(10) + .height(10) + .width(10) + .centerY() + + view.addSubview(chatWarningLab) + chatWarningLab.layoutChain + .edgesVertical(5) + .left(15) + .rightToLeftOfView(closeBtn, offset: -8) + + return view + }() + + lazy var closeBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.backgroundColor = .clear + btn.setImage(UIImage(named: "Group/close"), for: .normal) + btn.extendEdgeInsets = UIEdgeInsets(top: 20, left: 30, bottom: 20, right: 10) + btn.rx.tap.subscribe(onNext: { _ in + self.chatWarningView.isHidden = true + }).disposed(by: disposeBag) + return btn + }() + + lazy var chatWarningLab: UILabel = { + let label = UILabel() + label.textColor = .white + label.font = .systemFont(ofSize: 10, weight: .regular) + label.numberOfLines = 0 + return label + }() + // MARK: - Message List lazy var tableView: UITableView = { let tv = UITableView(frame: .zero, style: .plain) @@ -345,6 +413,32 @@ class GroupChatView: UIView { btn.cornerRadius = 17 return btn }() + + /// IM功能禁用 + lazy var disableIMView: 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 = 9 + view.isHidden = true + + let v = UIView() + v.backgroundColor = .white + v.cornerRadius = 25 + view.addSubview(v) + v.layoutChain.edges() + + let label = UILabel() + label.text = "开通VIP后可发送消息" + label.textColor = ThemeManager.shared.color.titleAuxColor + label.font = .systemFont(ofSize: 15, weight: .medium) + view.addSubview(label) + label.layoutChain.centerX().centerY() + + return view + }() /// 语音面板 lazy var voiceRecordView: VoiceRecordView = { diff --git a/QuickLocation/Section/Group/GroupChat/GroupChatViewModel.swift b/QuickLocation/Section/Group/GroupChat/GroupChatViewModel.swift index f5e515f..ba27fec 100644 --- a/QuickLocation/Section/Group/GroupChat/GroupChatViewModel.swift +++ b/QuickLocation/Section/Group/GroupChat/GroupChatViewModel.swift @@ -45,7 +45,11 @@ final class GroupChatViewModel { private let timeGapThreshold: TimeInterval = 300 // 5 minutes var groupModel: GroupInfoModel? - var memberList: [GroupMemberModel] = [] + var memberList: [GroupMemberModel] = [] { + didSet { + buildAvatarCache() + } + } var groupId: String = "" // MARK: - Init @@ -72,6 +76,43 @@ final class GroupChatViewModel { cache[member.user_id] = member.userIcon } avatarCache = cache + // 刷新已有消息的头像(并行加载时 loadMessages 可能先于 memberList 完成) + refreshMessageAvatars() + } + + /// 用 avatarCache 刷新已有消息的头像(并行加载时 loadMessages 可能先于 memberList 完成) + private func refreshMessageAvatars() { + var items = messagesSubject.value + var didChange = false + items = items.map { item in + switch item { + case var .send(m): if updateAvatar(&m) { didChange = true }; return .send(m) + case var .received(m): if updateAvatar(&m) { didChange = true }; return .received(m) + case var .emojiSend(m): if updateAvatar(&m) { didChange = true }; return .emojiSend(m) + case var .emojiReceived(m): if updateAvatar(&m) { didChange = true }; return .emojiReceived(m) + case var .voiceSend(m): if updateAvatar(&m) { didChange = true }; return .voiceSend(m) + case var .voiceReceived(m): if updateAvatar(&m) { didChange = true }; return .voiceReceived(m) + case var .imageSend(m): if updateAvatar(&m) { didChange = true }; return .imageSend(m) + case var .imageReceived(m): if updateAvatar(&m) { didChange = true }; return .imageReceived(m) + default: return item + } + } + if didChange { + messagesSubject.accept(items) + } + } + + /// 尝试用缓存更新单条消息的头像,返回是否变更 + private func updateAvatar(_ msg: inout ChatMessage) -> Bool { + guard let cached = avatarCache[msg.senderId], cached != msg.avatar else { return false } + msg = ChatMessage( + id: msg.id, isSelf: msg.isSelf, senderId: msg.senderId, + avatar: cached, senderName: msg.senderName, + content: msg.content, voiceUrl: msg.voiceUrl, imageUrl: msg.imageUrl, + imageWidth: msg.imageWidth, imageHeight: msg.imageHeight, + timestamp: msg.timestamp, showTime: msg.showTime, isUploading: msg.isUploading + ) + return true } func getUserAvatar(id: String) -> UIImage { @@ -81,7 +122,7 @@ final class GroupChatViewModel { avatarCache[id] = image return image } - return UIImage(named: "GroupIcon1") ?? UIImage() + return UIImage(named: "UserIcon/1") ?? UIImage() } func getUserNickName(id: String) -> String { @@ -141,6 +182,8 @@ final class GroupChatViewModel { } DispatchQueue.main.async { self.messagesSubject.accept(items) + // 进入会话后清除未读 + self.markAsRead() } }, onFailure: { code, msg in @@ -148,6 +191,14 @@ final class GroupChatViewModel { }) } + /// 标记当前会话为已读 + private func markAsRead() { + let conversationID = "sg_\(groupId)" + OIMManager.manager.markConversationMessage(asRead: conversationID, + onSuccess: nil, + onFailure: nil) + } + /// 本地消息(发送中) func appendLocalMessage(_ item: ChatSectionItem) { var items = messagesSubject.value @@ -272,10 +323,12 @@ final class GroupChatViewModel { imageW = 0; imageH = 0 } + let sendID = msg.sendID ?? "" return ChatMessage( id: msg.clientMsgID ?? UUID().uuidString, isSelf: isSelf, - avatar: getUserAvatar(id: msg.sendID ?? ""), + senderId: sendID, + avatar: getUserAvatar(id: sendID), senderName: msg.senderNickname ?? "", content: content, voiceUrl: voiceUrl, @@ -322,19 +375,27 @@ final class GroupChatViewModel { 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 opUser = json["opUser"] as? [String: Any] else { return nil } + let group = json["group"] as? [String: Any] else { return nil } switch contentType { - case 1501, 1510: - // 群创建通知 - let ownerID = group["ownerUserID"] as? String ?? "" - let ownerNickName = getUserNickName(id: ownerID) - let text = ownerID == AppContextManager.shared.userId ? "圈子已经创建" : "\(ownerNickName) 创建了圈子" + case 1501: + // 群创建通知(admin 创建) + guard let opUser = json["opUser"] as? [String: Any] else { return nil } + let groupID = opUser["groupID"] as? String ?? "" + let isOwner = groupID.contains(AppContextManager.shared.userId) + let text = isOwner ? "\(AppContextManager.shared.name) 创建了圈子" : "圈子已经创建" + return NSAttributedString(string: text) + + case 1510: + // 新成员加入通知 + guard let entrantUser = json["entrantUser"] as? [String: Any] else { return nil } + let nickName = entrantUser["nickname"] as? String ?? entrantUser["userID"] as? String ?? "" + let text = "\(nickName) 加入了圈子" return NSAttributedString(string: text) case 1520: // 群名称改变通知 + guard let opUser = json["opUser"] as? [String: Any] else { return nil } let opUserID = opUser["userID"] as? String ?? "" let opNickName = getUserNickName(id: opUserID) let newName = group["groupName"] as? String ?? "" @@ -343,7 +404,6 @@ final class GroupChatViewModel { 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 diff --git a/QuickLocation/Section/Group/GroupInfo/GroupInfoVC.swift b/QuickLocation/Section/Group/GroupInfo/GroupInfoVC.swift new file mode 100644 index 0000000..b173f80 --- /dev/null +++ b/QuickLocation/Section/Group/GroupInfo/GroupInfoVC.swift @@ -0,0 +1,59 @@ +// +// GroupInfoVC.swift +// QuickLocation +// +// Created by 八条 on 2026/6/12. +// + +import UIKit +import ObjectMapper +import RxSwift +import RxCocoa + +class GroupInfoVC: BaseViewController { + + fileprivate var rootView: GroupInfoView! + + override func loadView() { + rootView = GroupInfoView(frame: UIScreen.main.bounds) + view = rootView + } + + private let groupInModel: GroupInfoModel? + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + guard let model = groupInModel else { return } + + rootView.groupIcon.image = model.groupIcon + rootView.groupNameLab.text = model.name + + rootView.applyBtn.rx.tap.subscribe(onNext: { _ in + self.requestOperateGroup() + }).disposed(by: disposeBag) + } + + // MARK: - API + private func requestOperateGroup() { + guard let model = groupInModel else { return } + DLToast.showLoading() + GroupService.operate(opType: "join", + requestData: ["share_code" : model.share_code]).subscribe(onNext: { response in + DLToast.show(text: "申请成功") + }, onError: { (error) in }).disposed(by: disposeBag) + } + + // MARK: - Init + init(groupInfo: [String: Any]) { + self.groupInModel = GroupInfoModel.init(JSON: groupInfo) + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/QuickLocation/Section/Group/GroupInfo/GroupInfoView.swift b/QuickLocation/Section/Group/GroupInfo/GroupInfoView.swift new file mode 100644 index 0000000..b53b73b --- /dev/null +++ b/QuickLocation/Section/Group/GroupInfo/GroupInfoView.swift @@ -0,0 +1,131 @@ +// +// GroupInfoView.swift +// QuickLocation +// +// Created by 八条 on 2026/6/12. +// + +import UIKit +import RxSwift +import RxCocoa + +class GroupInfoView: 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(groupIcon) + addSubview(groupNameLab) + addSubview(applyBtn) + + 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) + + groupIcon.layoutChain + .topToBottomOfView(navBarView, offset: 24) + .width(80) + .height(80) + .centerX() + + groupNameLab.layoutChain + .topToBottomOfView(groupIcon, offset: 4) + .centerX() + + applyBtn.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 groupIcon: UIImageView = { + let view = UIImageView() + view.backgroundColor = .clear + view.cornerRadius = 40 + return view + }() + + lazy var groupNameLab: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 16, weight: .medium) + label.textColor = ThemeManager.shared.color.titleAuxColor + return label + }() + + lazy var applyBtn: 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 + }() + + override init(frame: CGRect) { + super.init(frame: .zero) + backgroundColor = .white + setupUI() + setupRx() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/QuickLocation/Section/Group/GroupSetting/GroupSettingVC.swift b/QuickLocation/Section/Group/GroupSetting/GroupSettingVC.swift index 4863bf9..76652a0 100644 --- a/QuickLocation/Section/Group/GroupSetting/GroupSettingVC.swift +++ b/QuickLocation/Section/Group/GroupSetting/GroupSettingVC.swift @@ -85,10 +85,22 @@ class GroupSettingVC: BaseViewController { AppRouter.push(Route.inviteJoin, userInfo: ["groupInfo": model.toJSON()]) }).disposed(by: disposeBag) + // 审核成员 + rootView.auditMemberView.rx.tapGesture.subscribe(onNext: { _ in + guard let model = self.viewModel.groupModel else { return } + AppRouter.push(Route.reviewMemberList, userInfo: ["groupId": model.group_key]) + }).disposed(by: disposeBag) + + // 移除圈子成员 + rootView.removeMemberView.rx.tapGesture.subscribe(onNext: { _ in + guard let model = self.viewModel.groupModel else { return } + AppRouter.push(Route.removeMember, userInfo: ["groupId": model.group_key]) + }).disposed(by: disposeBag) + // 解散圈子 rootView.dismissGroupView.rx.tapGesture.subscribe(onNext: { _ in VerificationPopView.show { - + self.requestDismissGroup() } }).disposed(by: disposeBag) @@ -164,6 +176,16 @@ class GroupSettingVC: BaseViewController { } }.disposed(by: disposeBag) } + + private func requestDismissGroup() { + DLToast.showLoading() + GroupService.dismiss(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 685534f..2ac9097 100644 --- a/QuickLocation/Section/Group/GroupViewController.swift +++ b/QuickLocation/Section/Group/GroupViewController.swift @@ -54,10 +54,9 @@ final class GroupViewController: BaseViewController { guard !hasSetupIMListeners else { return } hasSetupIMListeners = true - GroupIMService.shared.setConversationListener { [weak self] _ in - GroupIMService.shared.getConversationList { conversations in - self?.viewModel.updateConversations(conversations) - } + // 监听器回调已包含完整的会话列表,直接使用,避免重复 MJExtension 解析 + GroupIMService.shared.setConversationListener { [weak self] conversations in + self?.viewModel.updateConversations(conversations) } // 群组同步完成(如刚创建/加入的群)后刷新群列表,解决业务接口先返回、SDK 后同步的时序问题 @@ -105,6 +104,10 @@ final class GroupViewController: BaseViewController { .observe(on: MainScheduler.asyncInstance) .bind(to: rootView.joinedTableView.rx.items(dataSource: joinedDataSource)) .disposed(by: disposeBag) + + rootView.hotGroupsCollectionView.rx.modelSelected(GroupInfoModel.self) + .subscribe(viewModel.hotGroupCellAction.inputs) + .disposed(by: disposeBag) } private func reactiveAction() { diff --git a/QuickLocation/Section/Group/GroupViewModel.swift b/QuickLocation/Section/Group/GroupViewModel.swift index 6fb8766..20682c3 100644 --- a/QuickLocation/Section/Group/GroupViewModel.swift +++ b/QuickLocation/Section/Group/GroupViewModel.swift @@ -8,6 +8,7 @@ import RxSwift import RxCocoa import RxDataSources import OpenIMSDK +import ObjectMapper typealias HotGroupListSectionModel = SectionModel typealias CircleListSectionModel = SectionModel @@ -51,6 +52,13 @@ final class GroupViewModel { func loadHotGroupData(_ list: [GroupInfoModel]) { hotGroupSectionedItems.onNext(list.mapSection()) } + + lazy var hotGroupCellAction: Action = { this in + return Action { model in + AppRouter.push(Route.groupInfo, userInfo: ["groupInfo": model.toJSON()]) + return .empty() + } + }(self) /// 加载群列表(全量刷新) func loadIMGroups(_ groups: [OIMGroupInfo], conversations: [OIMConversationInfo] = []) { diff --git a/QuickLocation/Section/Group/RemoveMember/RemoveMemberVC.swift b/QuickLocation/Section/Group/RemoveMember/RemoveMemberVC.swift new file mode 100644 index 0000000..ecb9936 --- /dev/null +++ b/QuickLocation/Section/Group/RemoveMember/RemoveMemberVC.swift @@ -0,0 +1,118 @@ +// +// RemoveMemberVC.swift +// QuickLocation +// +// Created by 八条 on 2026/6/12. +// + +import UIKit +import RxSwift +import RxCocoa +import RxDataSources +import ObjectMapper + +class RemoveMemberVC: BaseViewController { + + fileprivate var rootView: RemoveMemberView! + + override func loadView() { + rootView = RemoveMemberView(frame: UIScreen.main.bounds) + view = rootView + } + + private var viewModel: RemoveMemberViewModel + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + bindViewModel() + requestGroupInfo() + } + + private func bindViewModel() { + viewModel.output.sectionedItems + .bind(to: rootView.tableView.rx.items(dataSource: dataSource)) + .disposed(by: disposeBag) + + viewModel.output.sectionedItems + .subscribe(onNext: { [weak self] sections in + let count = sections.first?.items.count ?? 0 + self?.rootView.updateHeight(itemCount: count) + }) + .disposed(by: disposeBag) + + rootView.tableView.rx.modelSelected(GroupMemberModel.self) + .subscribe(viewModel.cellAction.inputs) + .disposed(by: disposeBag) + + // selectedMembers 驱动删除按钮状态和文字 + let selectedCount = viewModel.selectedMembers + .map { $0.count } + .share(replay: 1) + + selectedCount + .map { $0 > 0 } + .bind(to: rootView.deleteBtn.rx.isEnabled) + .disposed(by: disposeBag) + + selectedCount + .map { "删除(\($0))" } + .bind(to: rootView.deleteBtn.rx.title()) + .disposed(by: disposeBag) + + rootView.deleteBtn.rx.tap.subscribe(onNext: { _ in + self.showConfirmPop(showCloseBtn: true, + title: "确定要移除成员吗?", + message: "成员将从当前圈子中移除", + confirmText: "否", + cancelText: "是") { + self.requestRemoveMember() + } + }).disposed(by: disposeBag) + } + + // MARK: - UITableViewDataSource + lazy private var dataSource: RxTableViewSectionedReloadDataSource = { + return RxTableViewSectionedReloadDataSource( + configureCell: { (_, tableView, indexPath, model) in + let cell: RemoveMemberCell = tableView.dequeueReusableCell(for: indexPath) + cell.configure(model, + isOwn: self.viewModel.groupId.contains(model.user_id), + isSelected: self.viewModel.isSelected(id: model.user_id)) + return cell + }) + }() + + // MARK: - API + private func requestGroupInfo() { + DLToast.showLoading() + GroupService.groupInfoByKey(viewModel.groupId).subscribe { response in + DLToast.dismiss() + guard let model = response.model else { return } + self.viewModel.groupModel = model + self.viewModel.loadData(response.list) + }.disposed(by: disposeBag) + } + + // MARK: - API删除成员 + private func requestRemoveMember() { + DLToast.showLoading() + GroupService.kickMember(requestData: ["group_key": viewModel.groupId, + "kick_userid": viewModel.selectedMembers.value]).subscribe { response in + DLToast.dismiss() + self.viewModel.selectedMembers.accept([]) + self.requestGroupInfo() + }.disposed(by: disposeBag) + } + + // MARK: - Init + init(groupId: String) { + self.viewModel = RemoveMemberViewModel(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/RemoveMember/RemoveMemberView.swift b/QuickLocation/Section/Group/RemoveMember/RemoveMemberView.swift new file mode 100644 index 0000000..1e21a8a --- /dev/null +++ b/QuickLocation/Section/Group/RemoveMember/RemoveMemberView.swift @@ -0,0 +1,304 @@ +// +// RemoveMemberView.swift +// QuickLocation +// +// Created by 八条 on 2026/6/12. +// + +import UIKit +import RxSwift +import RxCocoa + +class RemoveMemberView: UIView { + + var disposeBag = DisposeBag() + /// 单行高度 + static let rowHeight: CGFloat = 85 + + 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) + navBarView.addSubview(deleteBtn) + + addSubview(titleLab) + addSubview(cornerBgView) + addSubview(tableView) + + navBgView.layoutChain + .edges(excludingEdge: .bottom) + .height(kNaviHeight) + + navBarView.layoutChain + .edges(excludingEdge: .bottom) + .height(kNaviHeight) + + navTitleLabel.layoutChain + .top(kStatusBarHeight + 12) + .centerX() + + backBtn.layoutChain + .centerY(navTitleLabel) + .left(15) + .width(24) + .height(24) + + deleteBtn.layoutChain + .right(15) + .width(100) + .height(44) + .centerY(navTitleLabel) + + titleLab.layoutChain + .topToBottomOfView(navBarView, offset: 8) + .left(15) + + cornerBgView.layoutChain + .topToBottomOfView(titleLab, offset: 16) + .edgesHorzontal(15) + + tableView.layoutChain + .topToBottomOfView(titleLab, offset: 16) + .edgesHorzontal(15) + } + + /// 根据数据源数量更新高度,上限到 bottom = kSafeBottomMargin + 10 + func updateHeight(itemCount: Int) { + let contentH = CGFloat(itemCount) * Self.rowHeight + let maxH = dl.height - kNaviHeight - kSafeBottomMargin - 10 - 16 + let targetH = min(contentH, maxH) + + tableView.layoutChain.height(targetH) + cornerBgView.layoutChain.height(targetH) + tableView.isScrollEnabled = targetH > maxH + } + + private var listHeightConstraint: NSLayoutConstraint? + + 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 deleteBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setTitle("删除(0)", for: .normal) + btn.setTitleColor(UIColor(hexStr: "#FF383C"), for: .normal) + btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium) + btn.contentHorizontalAlignment = .right + return btn + }() + + lazy var titleLab: UILabel = { + let label = UILabel() + label.text = "圈子成员" + label.font = .systemFont(ofSize: 16, weight: .semibold) + label.textColor = ThemeManager.shared.color.titleAuxColor + return label + }() + + 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 = 85 + tableView.isScrollEnabled = false + tableView.register(RemoveMemberCell.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: - RemoveMemberCell +class RemoveMemberCell: UITableViewCell { + + var disposeBag = DisposeBag() + + func configure(_ model: GroupMemberModel, isOwn: Bool, isSelected: Bool) { + avaterImgView.image = model.userIcon + nameLab.text = model.nick_name + ownView.isHidden = !isOwn + selectedBtn.isHidden = isOwn + selectedBtn.isSelected = isSelected + } + + 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(stackView) + bgView.addSubview(lineView) + + bgView.layoutChain + .edgesHorzontal() + .edgesVertical() + + avaterImgView.layoutChain + .edgesVertical(20) + .left(15) + .height(45) + .widthToHeight(1) + + nameLab.layoutChain + .leftToRightOfView(avaterImgView, offset: 10) + .centerY(avaterImgView) + + stackView.layoutChain + .right(15) + .centerY() + + ownView.layoutChain.width(25).height(35) + selectedBtn.layoutChain.width(20).height(20) + + lineView.layoutChain + .height(0.5) + .edgesHorzontal(15) + .bottom() + } + + 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: .medium) + return label + }() + + lazy var stackView: UIStackView = { + let view = UIStackView(arrangedSubviews: [ownView, selectedBtn]) + view.axis = .horizontal + view.alignment = .trailing + view.spacing = 0 + view.backgroundColor = .clear + return view + }() + + lazy var ownView: UIView = { + let view = UIView() + view.backgroundColor = .clear + view.isHidden = true + + let icon = UIImageView(image: UIImage(named: "Common/owner")) + view.addSubview(icon) + icon.layoutChain + .top() + .centerX() + .width(18) + .height(18) + + let label = UILabel() + label.text = "圈主" + label.textColor = ThemeManager.shared.color.titleAuxColor + label.font = .systemFont(ofSize: 10, weight: .medium) + view.addSubview(label) + label.layoutChain + .topToBottomOfView(icon) + .centerX() + + return view + }() + + lazy var selectedBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setImage(UIImage(named: "Common/checkbox_20x20"), for: .normal) + btn.setImage(UIImage(named: "Common/delete_check"), for: .selected) + btn.isUserInteractionEnabled = false + btn.isHidden = true + return btn + }() + + lazy var lineView: UIView = { + let view = UIView() + view.backgroundColor = ThemeManager.shared.color.lineColor + return view + }() + + 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 + } + + override func prepareForReuse() { + super.prepareForReuse() + disposeBag = DisposeBag() + } +} diff --git a/QuickLocation/Section/Group/RemoveMember/RemoveMemberViewModel.swift b/QuickLocation/Section/Group/RemoveMember/RemoveMemberViewModel.swift new file mode 100644 index 0000000..5335860 --- /dev/null +++ b/QuickLocation/Section/Group/RemoveMember/RemoveMemberViewModel.swift @@ -0,0 +1,65 @@ +// +// RemoveMemberViewModel.swift +// QuickLocation +// +// Created by 八条 on 2026/6/12. +// + +import RxSwift +import RxCocoa +import RxDataSources +import ObjectMapper + +typealias RemoveMemberListSectionModel = SectionModel + +class RemoveMemberViewModel { + + let groupId: String + + var groupModel: GroupInfoModel? + + struct Output { + var sectionedItems: Observable<[ReviewMemberListSectionModel]> + } + + let output: Output + + private var disposeBag = DisposeBag() + private let sectionedItems = PublishSubject<[ReviewMemberListSectionModel]>() + private var list: [GroupMemberModel] = [] + + var selectedMembers = BehaviorRelay<[String]>(value: []) + + lazy var cellAction: Action = { this in + return Action { model in + let memberId = model.user_id + guard !self.groupId.contains(memberId) else { return .empty() } + + var current = this.selectedMembers.value + if let idx = current.firstIndex(of: memberId) { + current.remove(at: idx) + } else { + current.append(memberId) + } + this.selectedMembers.accept(current) + self.sectionedItems.onNext(self.list.mapSection()) + return .empty() + } + }(self) + + func isSelected(id: String) -> Bool { + (self.selectedMembers.value.first(where: { $0 == id }) != nil) + } + + func loadData(_ list: [GroupMemberModel]) { + self.list = list + sectionedItems.onNext(list.mapSection()) + } + + init(groupId: String) { + self.groupId = groupId + output = Output( + sectionedItems: sectionedItems.asObservable() + ) + } +} diff --git a/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVC.swift b/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVC.swift index 4a2be41..b10bd09 100644 --- a/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVC.swift +++ b/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVC.swift @@ -35,6 +35,13 @@ class ReviewMemberListVC: BaseViewController { viewModel.output.sectionedItems .bind(to: rootView.tableView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag) + + viewModel.output.sectionedItems + .subscribe(onNext: { [weak self] sections in + let count = sections.first?.items.count ?? 0 + self?.rootView.updateHeight(itemCount: count) + }) + .disposed(by: disposeBag) } // MARK: - UITableViewDataSource @@ -43,6 +50,15 @@ class ReviewMemberListVC: BaseViewController { configureCell: { (_, tableView, indexPath, model) in let cell: ReviewMemberCell = tableView.dequeueReusableCell(for: indexPath) cell.configure(model) + + cell.agreeBtn.rx.tap.subscribe(onNext: { _ in + self.requestReviewMember(type: 1, userId: model.user_id) + }).disposed(by: cell.disposeBag) + + cell.refuseBtn.rx.tap.subscribe(onNext: { _ in + self.requestReviewMember(type: 2, userId: model.user_id) + }).disposed(by: cell.disposeBag) + return cell }) }() @@ -52,7 +68,17 @@ class ReviewMemberListVC: BaseViewController { DLToast.showLoading() GroupService.reviewlist(requestData: ["group_key": viewModel.groupId]).subscribe { response in DLToast.dismiss() - + self.viewModel.loadData(response.list) + }.disposed(by: disposeBag) + } + + private func requestReviewMember(type: Int, userId: String) { + DLToast.showLoading() + GroupService.reviewMember(requestData: ["group_key": viewModel.groupId, + "review_op": type, + "review_userid": userId]).subscribe { response in + DLToast.dismiss() + self.requestReviewlist() }.disposed(by: disposeBag) } diff --git a/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVM.swift b/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVM.swift index 09a5856..fd32477 100644 --- a/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVM.swift +++ b/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListVM.swift @@ -26,6 +26,10 @@ struct ReviewMemberListViewModel { private var disposeBag = DisposeBag() private let sectionedItems = PublishSubject<[ReviewMemberListSectionModel]>() + func loadData(_ list: [GroupMemberModel]) { + sectionedItems.onNext(list.mapSection()) + } + init(groupId: String) { self.groupId = groupId output = Output( diff --git a/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListView.swift b/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListView.swift index d920782..426b576 100644 --- a/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListView.swift +++ b/QuickLocation/Section/Group/ReviewMemberList/ReviewMemberListView.swift @@ -12,49 +12,63 @@ import RxCocoa class ReviewMemberListView: UIView { var disposeBag = DisposeBag() - + /// 单行高度 + static let rowHeight: CGFloat = 85 + 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) - + .height(kNaviHeight) + 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) - + .topToBottomOfView(navBarView, offset: 16) + .edgesHorzontal(15) + tableView.layoutChain - .edges(excludingEdge: .bottom) - .bottom(kSafeBottomMargin + 10, relation: .greaterThanOrEqual) + .topToBottomOfView(navBarView, offset: 16) + .edgesHorzontal(15) } + + /// 根据数据源数量更新高度,上限到 bottom = kSafeBottomMargin + 10 + func updateHeight(itemCount: Int) { + let contentH = CGFloat(itemCount) * Self.rowHeight + let maxH = dl.height - kNaviHeight - kSafeBottomMargin - 10 - 16 + let targetH = min(contentH, maxH) + + tableView.layoutChain.height(targetH) + cornerBgView.layoutChain.height(targetH) + tableView.isScrollEnabled = targetH > maxH + } + + private var listHeightConstraint: NSLayoutConstraint? lazy var navBgView: UIImageView = { let iv = UIImageView() @@ -96,7 +110,8 @@ class ReviewMemberListView: UIView { let tableView = UITableView(frame: .zero, style: .plain) tableView.backgroundColor = .clear tableView.separatorStyle = .none - tableView.estimatedRowHeight = 77 + tableView.estimatedRowHeight = 85 + tableView.isScrollEnabled = false tableView.register(ReviewMemberCell.self) return tableView }() @@ -118,8 +133,11 @@ class ReviewMemberListView: UIView { // MARK: - ReviewMemberCell class ReviewMemberCell: UITableViewCell { + var disposeBag = DisposeBag() + func configure(_ model: GroupMemberModel) { - + avaterImgView.image = model.userIcon + nameLab.text = model.nick_name } override init(style: CellStyle, reuseIdentifier: String?) { @@ -135,6 +153,7 @@ class ReviewMemberCell: UITableViewCell { bgView.addSubview(nameLab) bgView.addSubview(agreeBtn) bgView.addSubview(refuseBtn) + bgView.addSubview(lineView) bgView.layoutChain .edgesHorzontal(15) @@ -152,15 +171,20 @@ class ReviewMemberCell: UITableViewCell { agreeBtn.layoutChain .centerY() - .right(15) - .width(34) - .height(15) + .right(5) + .width(50) + .height(30) refuseBtn.layoutChain .centerY() - .rightToLeftOfView(agreeBtn, offset: 8) - .width(34) - .height(15) + .rightToLeftOfView(agreeBtn, offset: -8) + .width(50) + .height(30) + + lineView.layoutChain + .height(0.5) + .edgesHorzontal(15) + .bottom() } lazy var bgView: UIView = { @@ -187,9 +211,9 @@ class ReviewMemberCell: UITableViewCell { let btn = UIButton(type: .custom) btn.setTitle("同意", for: .normal) btn.setTitleColor(.white, for: .normal) - btn.titleLabel?.font = .systemFont(ofSize: 10, weight: .regular) + btn.titleLabel?.font = .systemFont(ofSize: 14, weight: .regular) btn.setBackgroundColor(UIColor(hexStr: "#16B3FF"), for: .normal) - btn.cornerRadius = 7.5 + btn.cornerRadius = 15 return btn }() @@ -197,13 +221,19 @@ class ReviewMemberCell: UITableViewCell { 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.titleLabel?.font = .systemFont(ofSize: 14, weight: .regular) btn.setBackgroundColor(.white, for: .normal) btn.borderWidth = 0.5 btn.borderColor = UIColor(hexStr: "#16B3FF") - btn.cornerRadius = 7.5 + btn.cornerRadius = 15 return btn }() + + lazy var lineView: UIView = { + let view = UIView() + view.backgroundColor = ThemeManager.shared.color.lineColor + return view + }() required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -219,4 +249,9 @@ class ReviewMemberCell: UITableViewCell { // Configure the view for the selected state } + + override func prepareForReuse() { + super.prepareForReuse() + disposeBag = DisposeBag() + } } diff --git a/QuickLocation/Section/Home/HomeViewController.swift b/QuickLocation/Section/Home/HomeViewController.swift index 1742390..65b87fe 100644 --- a/QuickLocation/Section/Home/HomeViewController.swift +++ b/QuickLocation/Section/Home/HomeViewController.swift @@ -126,25 +126,31 @@ class HomeViewController: BaseViewController { Defaults[\.loginToken] = model.token AppContextManager.shared.systemConfig = model.config self.getUserIMToken() - self.requestUserInfo() - self.requestGroupInfo() + // 先更新用户信息(含头像),再拉群列表同步地图标注,避免头像旧 + self.requestUserInfo { [weak self] in + self?.requestGroupInfo() + } }).disposed(by: disposeBag) } /// 获取用户IM Token func getUserIMToken() { + DLToast.showLoading() 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 } + GroupIMService.shared.login { _ in + DLToast.dismiss() + } }).disposed(by: disposeBag) } - private func requestUserInfo() { + private func requestUserInfo(completion: (() -> Void)? = nil) { UserService.userInfo().subscribe { response in guard let model = response.model else { return } AppContextManager.shared.saveAccount(model) self.rootView.avatarImgView.image = model.userIcon + completion?() }.disposed(by: disposeBag) } diff --git a/QuickLocation/Section/Launch/LaunchViewController.swift b/QuickLocation/Section/Launch/LaunchViewController.swift index 4cf20ff..a322ef5 100644 --- a/QuickLocation/Section/Launch/LaunchViewController.swift +++ b/QuickLocation/Section/Launch/LaunchViewController.swift @@ -71,8 +71,7 @@ class LaunchViewController: BaseViewController { guard let model = response.model else { return } Defaults[\.loginToken] = model.token AppContextManager.shared.systemConfig = model.config - // 保存用户数据 -// AppContextManager.shared.saveAccount(model) + self.getUserIMToken() }, onError: { [weak self] (error) in DLAlert.show(title: error.localizedDescription, defaultTitle: "重试") { [weak self] in @@ -82,6 +81,16 @@ class LaunchViewController: BaseViewController { }).disposed(by: disposeBag) } + /// 获取用户IM Token + func getUserIMToken() { + DLToast.showLoading() + 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) + } + // MARK: - Init init() { super.init(nibName: nil, bundle: nil) diff --git a/QuickLocation/Section/Mine/CheckPermission/CheckPermissionVC.swift b/QuickLocation/Section/Mine/CheckPermission/CheckPermissionVC.swift new file mode 100644 index 0000000..b85bdcd --- /dev/null +++ b/QuickLocation/Section/Mine/CheckPermission/CheckPermissionVC.swift @@ -0,0 +1,172 @@ +// +// CheckPermissionVC.swift +// QuickLocation +// +// Created by 八条 on 2026/6/12. +// + +import UIKit +import RxSwift +import RxCocoa + +class CheckPermissionVC: BaseViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + setupUI() + + backBtn.rx.tap.subscribe(onNext: { _ in + AppRouter.shared.popOrDismiss() + }).disposed(by: disposeBag) + + tipsLab.rx.tapGesture.subscribe { _ in + AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.privacyPolicyUrl]) + }.disposed(by: disposeBag) + + settingBtn.rx.tap.subscribe(onNext: { _ in + guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return } + if UIApplication.shared.canOpenURL(settingsURL) { + UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil) + } + }).disposed(by: disposeBag) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + let manager = AuthorizeManager.manager(type: .locationAlways) + settingBtn.isEnabled = manager?.authorizeStatus() != .authorized + } + + private func setupUI() { + view.addSubview(navBgView) + view.addSubview(navBarView) + navBarView.addSubview(navTitleLabel) + navBarView.addSubview(backBtn) + + view.addSubview(scrollView) + scrollView.addSubview(scrollContentView) + scrollContentView.addSubview(stepImgView) + view.addSubview(tipsLab) + view.addSubview(settingBtn) + + navBgView.layoutChain + .edges(excludingEdge: .bottom) + .height(kNaviHeight) + + navBarView.layoutChain + .edges(excludingEdge: .bottom) + .height(kNaviHeight) + + navTitleLabel.layoutChain + .top(kStatusBarHeight + 12) + .centerX() + + backBtn.layoutChain + .centerY(navTitleLabel) + .left(15) + .width(24) + .height(24) + + settingBtn.layoutChain + .edgesHorzontal(30) + .bottom(kSafeBottomMargin + 30) + .height(50) + + tipsLab.layoutChain + .edgesHorzontal(31) + .bottomToTopOfView(settingBtn, offset: -20) + + scrollView.layoutChain + .topToBottomOfView(navBarView, offset: 15) + .edgesHorzontal() + .bottomToTopOfView(tipsLab, offset: -10) + + scrollContentView.layoutChain + .edges() + .widthToView(scrollView) + + stepImgView.layoutChain + .top() + .edgesHorzontal(20) + .bottom() + .heightToWidth(531/335) + } + + 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 scrollView: UIScrollView = { + let view = UIScrollView() + view.backgroundColor = .clear + return view + }() + + lazy var scrollContentView: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + + lazy var stepImgView: UIImageView = { + let view = UIImageView(image: UIImage(named: "CheckPermission/permission")) + view.contentMode = .scaleAspectFill + return view + }() + + lazy var tipsLab: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 10, weight: .regular) + label.textColor = UIColor(hexStr: "#333333") + label.textAlignment = .center + label.numberOfLines = 0 + label.isUserInteractionEnabled = true + + let text = "根据我们的隐私政策和用户设置,您的位置数据也将与第三方共享,用于研究、定制广告和分析目的。" + let attr = NSMutableAttributedString(string: text) + let range = (text as NSString).range(of: "隐私政策") + attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#16B3FF"), range: range) + label.attributedText = attr + return label + }() + + lazy var settingBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setTitle("去设置", for: .normal) + btn.setTitle("已设置", for: .disabled) + 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 + }() + +} diff --git a/QuickLocation/Section/Mine/MineViewModel.swift b/QuickLocation/Section/Mine/MineViewModel.swift index 197d7b8..1788780 100644 --- a/QuickLocation/Section/Mine/MineViewModel.swift +++ b/QuickLocation/Section/Mine/MineViewModel.swift @@ -44,6 +44,8 @@ class MineViewModel { AppRouter.push(Route.privacyPolicy) case "在线客服": this.requestWechatService() + case "权限检测": + AppRouter.push(Route.checkPermission) default: break } diff --git a/QuickLocation/Section/Scan/ScanVC.swift b/QuickLocation/Section/Scan/ScanVC.swift index e9013f2..23fefe0 100644 --- a/QuickLocation/Section/Scan/ScanVC.swift +++ b/QuickLocation/Section/Scan/ScanVC.swift @@ -65,7 +65,7 @@ class ScanVC: BaseViewController { private func requestJoinGroup(code: String) { DLToast.showLoading() GroupService.operate(opType: "join", requestData: ["share_code" : code]).subscribe(onNext: { response in - DLToast.show(text: "加入成功") { + DLToast.show(text: "申请成功") { NotificationCenter.default.post(name: .RefreshGroupInfoNotification, object: nil) AppRouter.shared.popOrDismiss() } diff --git a/QuickLocation/Section/Web/Controller/WebViewController.swift b/QuickLocation/Section/Web/Controller/WebViewController.swift index 33a8fca..d68c276 100644 --- a/QuickLocation/Section/Web/Controller/WebViewController.swift +++ b/QuickLocation/Section/Web/Controller/WebViewController.swift @@ -370,7 +370,7 @@ class FullscreenWebView: WKWebView { if self.isHome { self.navTitle = self.webView.title ?? "" } else { - self.navTitle = self.webView.title ?? ""//self.webView.title == "物联物美" ? "便民商圈" : self.webView.title ?? "" + self.navTitle = self.webView.title ?? "" } }).disposed(by: disposeBag) diff --git a/QuickLocation/Service/GroupService.swift b/QuickLocation/Service/GroupService.swift index 0164900..51e2baf 100644 --- a/QuickLocation/Service/GroupService.swift +++ b/QuickLocation/Service/GroupService.swift @@ -99,10 +99,26 @@ struct GroupService { /// 审核列表 /// - Parameters: /// - requestData:group_key - static func reviewlist(requestData: [String: Any]) -> Observable { + static func reviewlist(requestData: [String: Any]) -> Observable { let api = GroupAPI.operate(opType: "reviewlist", requestData: requestData).multiTarget return APIProvider.request(token: api) - .map(GroupInfoResponse.self) + .map(ReviewMemberListResponse.self) + .asObservable() + } + + /// 审核成员 + static func reviewMember(requestData: [String: Any]) -> Observable { + let api = GroupAPI.operate(opType: "review", requestData: requestData).multiTarget + return APIProvider.request(token: api) + .map(ResponseModel.self) + .asObservable() + } + + /// 踢出群聊 + static func kickMember(requestData: [String: Any]) -> Observable { + let api = GroupAPI.operate(opType: "kick", requestData: requestData).multiTarget + return APIProvider.request(token: api) + .map(ResponseModel.self) .asObservable() } @@ -115,4 +131,12 @@ struct GroupService { .map(GroupInfoResponse.self) .asObservable() } + + /// 解散群聊 + static func dismiss(requestData: [String: Any]) -> Observable { + let api = GroupAPI.operate(opType: "dismiss", requestData: requestData).multiTarget + return APIProvider.request(token: api) + .map(GroupInfoResponse.self) + .asObservable() + } }