|
|
@ -182,6 +182,12 @@
|
||||||
30BAB8532FCD337C00C33B5C /* GroupService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8522FCD337C00C33B5C /* GroupService.swift */; };
|
30BAB8532FCD337C00C33B5C /* GroupService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8522FCD337C00C33B5C /* GroupService.swift */; };
|
||||||
30BAB8632FCD716C00C33B5C /* JoinGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8622FCD716C00C33B5C /* JoinGroupVC.swift */; };
|
30BAB8632FCD716C00C33B5C /* JoinGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8622FCD716C00C33B5C /* JoinGroupVC.swift */; };
|
||||||
30BAB8652FCD718A00C33B5C /* JoinGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8642FCD718A00C33B5C /* JoinGroupView.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 */; };
|
30DC18522FD009CD0041DCD1 /* VipExpenseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */; };
|
||||||
30DC18542FD00C4A0041DCD1 /* VipRechargeVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */; };
|
30DC18542FD00C4A0041DCD1 /* VipRechargeVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */; };
|
||||||
30DC185A2FD11E7A0041DCD1 /* WebOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18562FD11E7A0041DCD1 /* WebOperations.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 = "<group>"; };
|
30BAB8622FCD716C00C33B5C /* JoinGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinGroupVC.swift; sourceTree = "<group>"; };
|
||||||
30BAB8642FCD718A00C33B5C /* JoinGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinGroupView.swift; sourceTree = "<group>"; };
|
30BAB8642FCD718A00C33B5C /* JoinGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinGroupView.swift; sourceTree = "<group>"; };
|
||||||
30C4C0112FDABC8C009215C1 /* QuickLocation.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QuickLocation.entitlements; sourceTree = "<group>"; };
|
30C4C0112FDABC8C009215C1 /* QuickLocation.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QuickLocation.entitlements; sourceTree = "<group>"; };
|
||||||
|
30C4C0152FDB91B8009215C1 /* CheckPermissionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckPermissionVC.swift; sourceTree = "<group>"; };
|
||||||
|
30C4C0182FDBF094009215C1 /* RemoveMemberVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveMemberVC.swift; sourceTree = "<group>"; };
|
||||||
|
30C4C01A2FDBF09D009215C1 /* RemoveMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveMemberView.swift; sourceTree = "<group>"; };
|
||||||
|
30C4C01C2FDBF557009215C1 /* RemoveMemberViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveMemberViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
30C4C01F2FDC0EC5009215C1 /* GroupInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInfoView.swift; sourceTree = "<group>"; };
|
||||||
|
30C4C0212FDC0ED3009215C1 /* GroupInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInfoVC.swift; sourceTree = "<group>"; };
|
||||||
30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipExpenseModel.swift; sourceTree = "<group>"; };
|
30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipExpenseModel.swift; sourceTree = "<group>"; };
|
||||||
30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipRechargeVM.swift; sourceTree = "<group>"; };
|
30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipRechargeVM.swift; sourceTree = "<group>"; };
|
||||||
30DC18552FD11E7A0041DCD1 /* NavigationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTitleView.swift; sourceTree = "<group>"; };
|
30DC18552FD11E7A0041DCD1 /* NavigationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTitleView.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -820,9 +832,11 @@
|
||||||
305A76252FCA8C7000227D26 /* GroupViewModel.swift */,
|
305A76252FCA8C7000227D26 /* GroupViewModel.swift */,
|
||||||
305A76232FCA8C7000227D26 /* GroupView.swift */,
|
305A76232FCA8C7000227D26 /* GroupView.swift */,
|
||||||
30EFF3B82FD8FC5200EB35D4 /* VerificationPopView.swift */,
|
30EFF3B82FD8FC5200EB35D4 /* VerificationPopView.swift */,
|
||||||
|
30C4C01E2FDC0EA6009215C1 /* GroupInfo */,
|
||||||
307073E42FD18A20004C37CC /* GroupChat */,
|
307073E42FD18A20004C37CC /* GroupChat */,
|
||||||
30EFF3A22FD7C58400EB35D4 /* GroupSetting */,
|
30EFF3A22FD7C58400EB35D4 /* GroupSetting */,
|
||||||
30EFF3B12FD8F19E00EB35D4 /* ReviewMemberList */,
|
30EFF3B12FD8F19E00EB35D4 /* ReviewMemberList */,
|
||||||
|
30C4C0172FDBF066009215C1 /* RemoveMember */,
|
||||||
3062E8B82FCEAC5600CEF511 /* CreateGroup */,
|
3062E8B82FCEAC5600CEF511 /* CreateGroup */,
|
||||||
30BAB8612FCD714700C33B5C /* Join */,
|
30BAB8612FCD714700C33B5C /* Join */,
|
||||||
30BAB84B2FCD2FA400C33B5C /* InviteJoin */,
|
30BAB84B2FCD2FA400C33B5C /* InviteJoin */,
|
||||||
|
|
@ -881,6 +895,7 @@
|
||||||
30EFF3BC2FD9585200EB35D4 /* Account */,
|
30EFF3BC2FD9585200EB35D4 /* Account */,
|
||||||
30EFF3D42FDA8EF200EB35D4 /* EmergencyContact */,
|
30EFF3D42FDA8EF200EB35D4 /* EmergencyContact */,
|
||||||
30EFF3E32FDAA92200EB35D4 /* PrivacyPolicy */,
|
30EFF3E32FDAA92200EB35D4 /* PrivacyPolicy */,
|
||||||
|
30C4C0122FDB9178009215C1 /* CheckPermission */,
|
||||||
);
|
);
|
||||||
path = Mine;
|
path = Mine;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -888,14 +903,14 @@
|
||||||
305A763A2FCA8C7000227D26 /* Section */ = {
|
305A763A2FCA8C7000227D26 /* Section */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
305A76262FCA8C7000227D26 /* Group */,
|
|
||||||
305A762A2FCA8C7000227D26 /* Home */,
|
305A762A2FCA8C7000227D26 /* Home */,
|
||||||
305A762C2FCA8C7000227D26 /* Launch */,
|
|
||||||
305A76312FCA8C7000227D26 /* Login */,
|
|
||||||
305A76352FCA8C7000227D26 /* Map */,
|
305A76352FCA8C7000227D26 /* Map */,
|
||||||
|
305A76262FCA8C7000227D26 /* Group */,
|
||||||
305A76392FCA8C7000227D26 /* Mine */,
|
305A76392FCA8C7000227D26 /* Mine */,
|
||||||
305A798E2FCAC5F600227D26 /* InviteMember */,
|
305A798E2FCAC5F600227D26 /* InviteMember */,
|
||||||
3062E8C52FCFD01000CEF511 /* VipRecharge */,
|
3062E8C52FCFD01000CEF511 /* VipRecharge */,
|
||||||
|
305A76312FCA8C7000227D26 /* Login */,
|
||||||
|
305A762C2FCA8C7000227D26 /* Launch */,
|
||||||
3062E8B32FCE6BA400CEF511 /* Scan */,
|
3062E8B32FCE6BA400CEF511 /* Scan */,
|
||||||
30DC18592FD11E7A0041DCD1 /* Web */,
|
30DC18592FD11E7A0041DCD1 /* Web */,
|
||||||
30EFF3AD2FD7FF1400EB35D4 /* TextInput */,
|
30EFF3AD2FD7FF1400EB35D4 /* TextInput */,
|
||||||
|
|
@ -1132,6 +1147,33 @@
|
||||||
path = Join;
|
path = Join;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
30C4C0122FDB9178009215C1 /* CheckPermission */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
30C4C0152FDB91B8009215C1 /* CheckPermissionVC.swift */,
|
||||||
|
);
|
||||||
|
path = CheckPermission;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
30C4C0172FDBF066009215C1 /* RemoveMember */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
30C4C0182FDBF094009215C1 /* RemoveMemberVC.swift */,
|
||||||
|
30C4C01C2FDBF557009215C1 /* RemoveMemberViewModel.swift */,
|
||||||
|
30C4C01A2FDBF09D009215C1 /* RemoveMemberView.swift */,
|
||||||
|
);
|
||||||
|
path = RemoveMember;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
30C4C01E2FDC0EA6009215C1 /* GroupInfo */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
30C4C0212FDC0ED3009215C1 /* GroupInfoVC.swift */,
|
||||||
|
30C4C01F2FDC0EC5009215C1 /* GroupInfoView.swift */,
|
||||||
|
);
|
||||||
|
path = GroupInfo;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
30DC18582FD11E7A0041DCD1 /* Controller */ = {
|
30DC18582FD11E7A0041DCD1 /* Controller */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -1173,8 +1215,8 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
30EFF3B42FD8F1D000EB35D4 /* ReviewMemberListVC.swift */,
|
30EFF3B42FD8F1D000EB35D4 /* ReviewMemberListVC.swift */,
|
||||||
30EFF3B22FD8F1C200EB35D4 /* ReviewMemberListView.swift */,
|
|
||||||
30EFF3B62FD8F86200EB35D4 /* ReviewMemberListVM.swift */,
|
30EFF3B62FD8F86200EB35D4 /* ReviewMemberListVM.swift */,
|
||||||
|
30EFF3B22FD8F1C200EB35D4 /* ReviewMemberListView.swift */,
|
||||||
);
|
);
|
||||||
path = ReviewMemberList;
|
path = ReviewMemberList;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1412,6 +1454,7 @@
|
||||||
305A768D2FCA8C7000227D26 /* AppNetworkConfig.swift in Sources */,
|
305A768D2FCA8C7000227D26 /* AppNetworkConfig.swift in Sources */,
|
||||||
305A768E2FCA8C7000227D26 /* SignPlugin.swift in Sources */,
|
305A768E2FCA8C7000227D26 /* SignPlugin.swift in Sources */,
|
||||||
305A768F2FCA8C7000227D26 /* SystemAPI.swift in Sources */,
|
305A768F2FCA8C7000227D26 /* SystemAPI.swift in Sources */,
|
||||||
|
30C4C0222FDC0ED3009215C1 /* GroupInfoVC.swift in Sources */,
|
||||||
305A76902FCA8C7000227D26 /* UserAPI.swift in Sources */,
|
305A76902FCA8C7000227D26 /* UserAPI.swift in Sources */,
|
||||||
30EFF3DE2FDA982C00EB35D4 /* EmergencyContactAddVC.swift in Sources */,
|
30EFF3DE2FDA982C00EB35D4 /* EmergencyContactAddVC.swift in Sources */,
|
||||||
305A76912FCA8C7000227D26 /* Constant.swift in Sources */,
|
305A76912FCA8C7000227D26 /* Constant.swift in Sources */,
|
||||||
|
|
@ -1422,6 +1465,7 @@
|
||||||
305A76942FCA8C7000227D26 /* UploadImageCell.swift in Sources */,
|
305A76942FCA8C7000227D26 /* UploadImageCell.swift in Sources */,
|
||||||
305A76952FCA8C7000227D26 /* CornerRadiusCell.swift in Sources */,
|
305A76952FCA8C7000227D26 /* CornerRadiusCell.swift in Sources */,
|
||||||
305A76962FCA8C7000227D26 /* CornerRadiusFooterView.swift in Sources */,
|
305A76962FCA8C7000227D26 /* CornerRadiusFooterView.swift in Sources */,
|
||||||
|
30C4C0162FDB91B8009215C1 /* CheckPermissionVC.swift in Sources */,
|
||||||
305A76972FCA8C7000227D26 /* CornerRadiusHeaderView.swift in Sources */,
|
305A76972FCA8C7000227D26 /* CornerRadiusHeaderView.swift in Sources */,
|
||||||
305A76982FCA8C7000227D26 /* ImagePicker.swift in Sources */,
|
305A76982FCA8C7000227D26 /* ImagePicker.swift in Sources */,
|
||||||
305A76992FCA8C7000227D26 /* ImagePickerPopup.swift in Sources */,
|
305A76992FCA8C7000227D26 /* ImagePickerPopup.swift in Sources */,
|
||||||
|
|
@ -1478,6 +1522,7 @@
|
||||||
305A76BD2FCA8C7000227D26 /* PaginationModel.swift in Sources */,
|
305A76BD2FCA8C7000227D26 /* PaginationModel.swift in Sources */,
|
||||||
305A76BE2FCA8C7000227D26 /* ResponseModel.swift in Sources */,
|
305A76BE2FCA8C7000227D26 /* ResponseModel.swift in Sources */,
|
||||||
30EFF3C02FD958AE00EB35D4 /* AccountVC.swift in Sources */,
|
30EFF3C02FD958AE00EB35D4 /* AccountVC.swift in Sources */,
|
||||||
|
30C4C0202FDC0EC5009215C1 /* GroupInfoView.swift in Sources */,
|
||||||
305A76BF2FCA8C7000227D26 /* ListService.swift in Sources */,
|
305A76BF2FCA8C7000227D26 /* ListService.swift in Sources */,
|
||||||
305A76C02FCA8C7000227D26 /* BaseNavigationController.swift in Sources */,
|
305A76C02FCA8C7000227D26 /* BaseNavigationController.swift in Sources */,
|
||||||
305A76C12FCA8C7000227D26 /* BaseViewController.swift in Sources */,
|
305A76C12FCA8C7000227D26 /* BaseViewController.swift in Sources */,
|
||||||
|
|
@ -1489,6 +1534,7 @@
|
||||||
305A76C32FCA8C7000227D26 /* MainTabBarController.swift in Sources */,
|
305A76C32FCA8C7000227D26 /* MainTabBarController.swift in Sources */,
|
||||||
30EFF3CA2FDA575600EB35D4 /* CancellationPopView.swift in Sources */,
|
30EFF3CA2FDA575600EB35D4 /* CancellationPopView.swift in Sources */,
|
||||||
305A76C42FCA8C7000227D26 /* QuickLocationTabBar.swift in Sources */,
|
305A76C42FCA8C7000227D26 /* QuickLocationTabBar.swift in Sources */,
|
||||||
|
30C4C01D2FDBF557009215C1 /* RemoveMemberViewModel.swift in Sources */,
|
||||||
305A76C52FCA8C7000227D26 /* Account.swift in Sources */,
|
305A76C52FCA8C7000227D26 /* Account.swift in Sources */,
|
||||||
305A76C62FCA8C7000227D26 /* AppContextManager.swift in Sources */,
|
305A76C62FCA8C7000227D26 /* AppContextManager.swift in Sources */,
|
||||||
305A76C72FCA8C7000227D26 /* UserConfigModel.swift in Sources */,
|
305A76C72FCA8C7000227D26 /* UserConfigModel.swift in Sources */,
|
||||||
|
|
@ -1556,6 +1602,7 @@
|
||||||
305A76F02FCA8C7000227D26 /* MineViewModel.swift in Sources */,
|
305A76F02FCA8C7000227D26 /* MineViewModel.swift in Sources */,
|
||||||
305A76F12FCA8C7000227D26 /* SystemService.swift in Sources */,
|
305A76F12FCA8C7000227D26 /* SystemService.swift in Sources */,
|
||||||
30DC18522FD009CD0041DCD1 /* VipExpenseModel.swift in Sources */,
|
30DC18522FD009CD0041DCD1 /* VipExpenseModel.swift in Sources */,
|
||||||
|
30C4C01B2FDBF09D009215C1 /* RemoveMemberView.swift in Sources */,
|
||||||
307073E12FD15F50004C37CC /* GroupIMService.swift in Sources */,
|
307073E12FD15F50004C37CC /* GroupIMService.swift in Sources */,
|
||||||
305A76F22FCA8C7000227D26 /* UserService.swift in Sources */,
|
305A76F22FCA8C7000227D26 /* UserService.swift in Sources */,
|
||||||
305A76F32FCA8C7000227D26 /* AutoLayout+NSLayoutConstraint.swift in Sources */,
|
305A76F32FCA8C7000227D26 /* AutoLayout+NSLayoutConstraint.swift in Sources */,
|
||||||
|
|
@ -1575,6 +1622,7 @@
|
||||||
305A76FD2FCA8C7000227D26 /* EmptyDataSetSource.swift in Sources */,
|
305A76FD2FCA8C7000227D26 /* EmptyDataSetSource.swift in Sources */,
|
||||||
30EFF3CD2FDA668A00EB35D4 /* MyProfileView.swift in Sources */,
|
30EFF3CD2FDA668A00EB35D4 /* MyProfileView.swift in Sources */,
|
||||||
305A76FE2FCA8C7000227D26 /* EmptyDataSetView.swift in Sources */,
|
305A76FE2FCA8C7000227D26 /* EmptyDataSetView.swift in Sources */,
|
||||||
|
30C4C0192FDBF094009215C1 /* RemoveMemberVC.swift in Sources */,
|
||||||
305A76FF2FCA8C7000227D26 /* EmptyDataSetView+Extension.swift in Sources */,
|
305A76FF2FCA8C7000227D26 /* EmptyDataSetView+Extension.swift in Sources */,
|
||||||
305A77002FCA8C7000227D26 /* RefreshStyle.swift in Sources */,
|
305A77002FCA8C7000227D26 /* RefreshStyle.swift in Sources */,
|
||||||
305A77012FCA8C7000227D26 /* DLHUD.swift in Sources */,
|
305A77012FCA8C7000227D26 /* DLHUD.swift in Sources */,
|
||||||
|
|
@ -1643,7 +1691,7 @@
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 434CGNSJ28;
|
DEVELOPMENT_TEAM = 7LP48T8ZJE;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = "$(inherited)";
|
ENABLE_USER_SCRIPT_SANDBOXING = "$(inherited)";
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = QuickLocation/Info.plist;
|
INFOPLIST_FILE = QuickLocation/Info.plist;
|
||||||
|
|
@ -1664,7 +1712,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = cn.zuom8.jisulocation;
|
PRODUCT_BUNDLE_IDENTIFIER = cn.zuom8.jisuloca;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
|
@ -1692,7 +1740,7 @@
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 434CGNSJ28;
|
DEVELOPMENT_TEAM = 7LP48T8ZJE;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = "$(inherited)";
|
ENABLE_USER_SCRIPT_SANDBOXING = "$(inherited)";
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = QuickLocation/Info.plist;
|
INFOPLIST_FILE = QuickLocation/Info.plist;
|
||||||
|
|
@ -1713,7 +1761,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = cn.zuom8.jisulocation;
|
PRODUCT_BUNDLE_IDENTIFIER = cn.zuom8.jisuloca;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"provides-namespace" : true
|
||||||
|
}
|
||||||
|
}
|
||||||
22
QuickLocation/Assets.xcassets/CheckPermission/permission.imageset/Contents.json
vendored
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/CheckPermission/permission.imageset/permission@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
QuickLocation/Assets.xcassets/CheckPermission/permission.imageset/permission@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 188 KiB |
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/Common/checkbox_20x20.imageset/Rectangle_281@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
QuickLocation/Assets.xcassets/Common/checkbox_20x20.imageset/Rectangle_281@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
|
@ -33,8 +33,8 @@ class BaseViewController: UIViewController {
|
||||||
/// 导航栏标题
|
/// 导航栏标题
|
||||||
var navTitle: String = "" {
|
var navTitle: String = "" {
|
||||||
didSet {
|
didSet {
|
||||||
// backButton.setTitle(" \(navTitle)", for: .normal)
|
backButton.setTitle(" \(navTitle)", for: .normal)
|
||||||
navigationItem.title = navTitle
|
// navigationItem.title = navTitle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,10 @@ class AppContextManager: NSObject {
|
||||||
var sex: Int {
|
var sex: Int {
|
||||||
account?.sex ?? -1
|
account?.sex ?? -1
|
||||||
}
|
}
|
||||||
|
/// VIP
|
||||||
|
var vip: Int {
|
||||||
|
account?.vip ?? 1
|
||||||
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public func saveAccount(_ account: UserConfigModel) -> Bool {
|
public func saveAccount(_ account: UserConfigModel) -> Bool {
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ struct UserConfigModel: Mappable {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SystemConfigModel: Mappable {
|
struct SystemConfigModel: Mappable {
|
||||||
|
/// 聊天界面的警告说明
|
||||||
|
var chatWarning: String = ""
|
||||||
|
|
||||||
/// 圈子里的banner
|
/// 圈子里的banner
|
||||||
var groupBannerList: [String] = []
|
var groupBannerList: [String] = []
|
||||||
|
|
||||||
|
|
@ -66,6 +69,7 @@ struct SystemConfigModel: Mappable {
|
||||||
}
|
}
|
||||||
|
|
||||||
mutating func mapping(map: Map) {
|
mutating func mapping(map: Map) {
|
||||||
|
chatWarning <- map["client.chatwarning", nested: false]
|
||||||
groupBannerList <- map["client.team.ad", nested: false]
|
groupBannerList <- map["client.team.ad", nested: false]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,14 @@ enum Route: String {
|
||||||
case vipRights = "vipRights"
|
case vipRights = "vipRights"
|
||||||
/// 群聊
|
/// 群聊
|
||||||
case groupChat = "groupChat"
|
case groupChat = "groupChat"
|
||||||
|
/// 圈子资料
|
||||||
|
case groupInfo = "groupInfo"
|
||||||
/// 圈子设置
|
/// 圈子设置
|
||||||
case groupSetting = "groupSetting"
|
case groupSetting = "groupSetting"
|
||||||
|
/// 待审核列表
|
||||||
|
case reviewMemberList = "reviewMemberList"
|
||||||
|
/// 移除圈子成员
|
||||||
|
case removeMember = "removeMember"
|
||||||
/// 账号与安全
|
/// 账号与安全
|
||||||
case account = "account"
|
case account = "account"
|
||||||
/// 更换手机号
|
/// 更换手机号
|
||||||
|
|
@ -45,6 +51,8 @@ enum Route: String {
|
||||||
case emergencyContactAdd = "emergencyContactAdd"
|
case emergencyContactAdd = "emergencyContactAdd"
|
||||||
/// 隐私与协议
|
/// 隐私与协议
|
||||||
case privacyPolicy = "privacyPolicy"
|
case privacyPolicy = "privacyPolicy"
|
||||||
|
/// 权限检测
|
||||||
|
case checkPermission = "checkPermission"
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Route: RouterTarget {
|
extension Route: RouterTarget {
|
||||||
|
|
@ -181,12 +189,30 @@ extension AppRouter: AppRouterProtocol {
|
||||||
return GroupChatVC(groupId: groupId)
|
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: - 圈子设置
|
// MARK: - 圈子设置
|
||||||
AppRouter.register(Route.groupSetting) { url, parameters in
|
AppRouter.register(Route.groupSetting) { url, parameters in
|
||||||
let groupId = parameters["groupId"].safeString
|
let groupId = parameters["groupId"].safeString
|
||||||
return GroupSettingVC(groupId: groupId)
|
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: - 账号与安全
|
// MARK: - 账号与安全
|
||||||
AppRouter.register(Route.account) { url, parameters in
|
AppRouter.register(Route.account) { url, parameters in
|
||||||
let vc = AccountVC()
|
let vc = AccountVC()
|
||||||
|
|
@ -226,6 +252,11 @@ extension AppRouter: AppRouterProtocol {
|
||||||
AppRouter.register(Route.privacyPolicy) { url, parameters in
|
AppRouter.register(Route.privacyPolicy) { url, parameters in
|
||||||
PrivacyPolicyVC()
|
PrivacyPolicyVC()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - 权限检测
|
||||||
|
AppRouter.register(Route.checkPermission) { url, parameters in
|
||||||
|
CheckPermissionVC()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,8 @@ struct GroupInfoResponse: BaseModelProtocol {
|
||||||
var model: GroupInfoModel?
|
var model: GroupInfoModel?
|
||||||
// 圈子成员列表
|
// 圈子成员列表
|
||||||
var list: [GroupMemberModel] = []
|
var list: [GroupMemberModel] = []
|
||||||
|
// 待审核数量
|
||||||
|
var reviewCount: Int = 0
|
||||||
|
|
||||||
init?(map: Map) {}
|
init?(map: Map) {}
|
||||||
|
|
||||||
|
|
@ -62,6 +64,24 @@ struct GroupInfoResponse: BaseModelProtocol {
|
||||||
message <- map["msg"]
|
message <- map["msg"]
|
||||||
model <- map["data.attr"]
|
model <- map["data.attr"]
|
||||||
list <- map["data.employee"]
|
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 description: String = ""
|
||||||
/// 会员等级
|
/// 会员等级
|
||||||
var level: String = ""
|
var level: String = ""
|
||||||
|
/// 邀请码
|
||||||
|
var share_code: String = ""
|
||||||
|
|
||||||
init?(map: Map) {
|
init?(map: Map) {
|
||||||
|
|
||||||
|
|
@ -129,6 +151,7 @@ struct GroupInfoModel: Mappable, Equatable {
|
||||||
level <- map["level"]
|
level <- map["level"]
|
||||||
review <- map["review"]
|
review <- map["review"]
|
||||||
description <- map["description"]
|
description <- map["description"]
|
||||||
|
share_code <- map["share_code"]
|
||||||
people_no <- (map["people_no"], kStrTransformInt)
|
people_no <- (map["people_no"], kStrTransformInt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict/>
|
||||||
<key>com.apple.developer.associated-domains</key>
|
|
||||||
<array>
|
|
||||||
<string>applinks:smartdrive.zuom8.cn</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -46,16 +46,26 @@ final class GroupChatVC: BaseViewController {
|
||||||
setupMessageListener()
|
setupMessageListener()
|
||||||
setupVoiceRecording()
|
setupVoiceRecording()
|
||||||
setupPanelDismiss()
|
setupPanelDismiss()
|
||||||
setupKeyboard()
|
|
||||||
// 并行加载:业务接口 + IM SDK 互不依赖,同时发起
|
// 并行加载:业务接口 + IM SDK 互不依赖,同时发起
|
||||||
requestGroupInfoByKey()
|
requestGroupInfoByKey()
|
||||||
viewModel.loadMessages()
|
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) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
setupKeyboard()
|
||||||
IQKeyboardManager.shared.isEnabled = false
|
IQKeyboardManager.shared.isEnabled = false
|
||||||
IQKeyboardManager.shared.resignOnTouchOutside = 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) {
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
|
@ -65,12 +75,19 @@ final class GroupChatVC: BaseViewController {
|
||||||
IQKeyboardManager.shared.resignOnTouchOutside = true
|
IQKeyboardManager.shared.resignOnTouchOutside = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
|
super.viewDidDisappear(animated)
|
||||||
|
NotificationCenter.default.removeObserver(self)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Keyboard
|
// MARK: - Keyboard
|
||||||
private func setupKeyboard() {
|
private func setupKeyboard() {
|
||||||
// 键盘升起
|
// 键盘升起
|
||||||
NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
|
NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
|
||||||
.subscribe(onNext: { [weak self] noti in
|
.subscribe(onNext: { [weak self] noti in
|
||||||
guard let self = self,
|
guard let self = self,
|
||||||
|
// 只在 VC 本身可见时响应,避免导航栈下层 VC 被全局通知唤醒
|
||||||
|
self.isViewLoaded && self.view.window != nil,
|
||||||
let userInfo = noti.userInfo,
|
let userInfo = noti.userInfo,
|
||||||
let frame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect
|
let frame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect
|
||||||
else { return }
|
else { return }
|
||||||
|
|
@ -165,6 +182,10 @@ final class GroupChatVC: BaseViewController {
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.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
|
rootView.voiceBtn.rx.tap.subscribe(onNext: { [weak self] _ in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
@ -410,10 +431,10 @@ final class GroupChatVC: BaseViewController {
|
||||||
GroupService.groupInfoByKey(viewModel.groupId).subscribe { response in
|
GroupService.groupInfoByKey(viewModel.groupId).subscribe { response in
|
||||||
guard let model = response.model else { return }
|
guard let model = response.model else { return }
|
||||||
self.viewModel.memberList = response.list
|
self.viewModel.memberList = response.list
|
||||||
self.viewModel.buildAvatarCache()
|
|
||||||
self.rootView.groupNameLabel.text = model.name
|
self.rootView.groupNameLabel.text = model.name
|
||||||
self.rootView.groupAvatarView.image = model.groupIcon
|
self.rootView.groupAvatarView.image = model.groupIcon
|
||||||
self.rootView.reviewBtn.isHidden = !model.is_owner
|
self.rootView.reviewBtn.isHidden = !model.is_owner
|
||||||
|
self.rootView.reviewDotView.isHidden = response.reviewCount == 0
|
||||||
}.disposed(by: disposeBag)
|
}.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -517,6 +538,7 @@ extension GroupChatVC: PhotoPickerControllerDelegate {
|
||||||
let localMsg = ChatMessage(
|
let localMsg = ChatMessage(
|
||||||
id: localId,
|
id: localId,
|
||||||
isSelf: true,
|
isSelf: true,
|
||||||
|
senderId: AppContextManager.shared.userId,
|
||||||
avatar: viewModel.getUserAvatar(id: AppContextManager.shared.userId),
|
avatar: viewModel.getUserAvatar(id: AppContextManager.shared.userId),
|
||||||
senderName: AppContextManager.shared.name,
|
senderName: AppContextManager.shared.name,
|
||||||
content: "",
|
content: "",
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ enum VoiceRecordState {
|
||||||
struct ChatMessage {
|
struct ChatMessage {
|
||||||
let id: String
|
let id: String
|
||||||
let isSelf: Bool
|
let isSelf: Bool
|
||||||
|
let senderId: String
|
||||||
let avatar: UIImage
|
let avatar: UIImage
|
||||||
let senderName: String
|
let senderName: String
|
||||||
let content: String
|
let content: String
|
||||||
|
|
@ -78,9 +79,12 @@ class GroupChatView: UIView {
|
||||||
rightIconsView.addSubview(reviewBtn)
|
rightIconsView.addSubview(reviewBtn)
|
||||||
rightIconsView.addSubview(memberBtn)
|
rightIconsView.addSubview(memberBtn)
|
||||||
rightIconsView.addSubview(settingBtn)
|
rightIconsView.addSubview(settingBtn)
|
||||||
|
rightIconsView.addSubview(reviewDotView)
|
||||||
|
|
||||||
addSubview(tableView)
|
addSubview(tableView)
|
||||||
|
addSubview(chatWarningView)
|
||||||
addSubview(bottomBar)
|
addSubview(bottomBar)
|
||||||
|
addSubview(disableIMView)
|
||||||
bottomBar.addSubview(bottomBarCornerView)
|
bottomBar.addSubview(bottomBarCornerView)
|
||||||
addSubview(voiceRecordView)
|
addSubview(voiceRecordView)
|
||||||
addSubview(emojiPanelView)
|
addSubview(emojiPanelView)
|
||||||
|
|
@ -127,6 +131,12 @@ class GroupChatView: UIView {
|
||||||
.left().centerY()
|
.left().centerY()
|
||||||
.width(24).height(24)
|
.width(24).height(24)
|
||||||
|
|
||||||
|
reviewDotView.layoutChain
|
||||||
|
.topToView(reviewBtn, offset: -2)
|
||||||
|
.leftToRightOfView(reviewBtn, offset: -6)
|
||||||
|
.width(8)
|
||||||
|
.height(8)
|
||||||
|
|
||||||
memberBtn.layoutChain
|
memberBtn.layoutChain
|
||||||
.leftToRightOfView(reviewBtn, offset: 20)
|
.leftToRightOfView(reviewBtn, offset: 20)
|
||||||
.centerY()
|
.centerY()
|
||||||
|
|
@ -143,11 +153,20 @@ class GroupChatView: UIView {
|
||||||
.edgesHorzontal()
|
.edgesHorzontal()
|
||||||
.bottomToTopOfView(bottomBar)
|
.bottomToTopOfView(bottomBar)
|
||||||
|
|
||||||
|
chatWarningView.layoutChain
|
||||||
|
.topToBottomOfView(navBarView)
|
||||||
|
.edgesHorzontal()
|
||||||
|
|
||||||
bottomBar.layoutChain
|
bottomBar.layoutChain
|
||||||
.edgesHorzontal(15)
|
.edgesHorzontal(15)
|
||||||
.height(50)
|
.height(50)
|
||||||
.bottom(kSafeBottomMargin + 20)
|
.bottom(kSafeBottomMargin + 20)
|
||||||
|
|
||||||
|
disableIMView.layoutChain
|
||||||
|
.edgesHorzontal(15)
|
||||||
|
.height(50)
|
||||||
|
.bottom(kSafeBottomMargin + 20)
|
||||||
|
|
||||||
bottomBarBottomConstraint = bottomBar.jh_constraint(.bottom, toAttribute: .bottom, otherView: bottomBar.superview, relation: .equal)
|
bottomBarBottomConstraint = bottomBar.jh_constraint(.bottom, toAttribute: .bottom, otherView: bottomBar.superview, relation: .equal)
|
||||||
|
|
||||||
bottomBarCornerView.layoutChain.edges()
|
bottomBarCornerView.layoutChain.edges()
|
||||||
|
|
@ -254,6 +273,14 @@ class GroupChatView: UIView {
|
||||||
return btn
|
return btn
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
lazy var reviewDotView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .red
|
||||||
|
view.cornerRadius = 4
|
||||||
|
view.isHidden = true
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
lazy var memberBtn: UIButton = {
|
lazy var memberBtn: UIButton = {
|
||||||
let btn = UIButton(type: .custom)
|
let btn = UIButton(type: .custom)
|
||||||
btn.setImage(UIImage(named: "IM/member"), for: .normal)
|
btn.setImage(UIImage(named: "IM/member"), for: .normal)
|
||||||
|
|
@ -266,6 +293,47 @@ class GroupChatView: UIView {
|
||||||
return btn
|
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
|
// MARK: - Message List
|
||||||
lazy var tableView: UITableView = {
|
lazy var tableView: UITableView = {
|
||||||
let tv = UITableView(frame: .zero, style: .plain)
|
let tv = UITableView(frame: .zero, style: .plain)
|
||||||
|
|
@ -346,6 +414,32 @@ class GroupChatView: UIView {
|
||||||
return btn
|
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 = {
|
lazy var voiceRecordView: VoiceRecordView = {
|
||||||
let v = VoiceRecordView()
|
let v = VoiceRecordView()
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,11 @@ final class GroupChatViewModel {
|
||||||
private let timeGapThreshold: TimeInterval = 300 // 5 minutes
|
private let timeGapThreshold: TimeInterval = 300 // 5 minutes
|
||||||
|
|
||||||
var groupModel: GroupInfoModel?
|
var groupModel: GroupInfoModel?
|
||||||
var memberList: [GroupMemberModel] = []
|
var memberList: [GroupMemberModel] = [] {
|
||||||
|
didSet {
|
||||||
|
buildAvatarCache()
|
||||||
|
}
|
||||||
|
}
|
||||||
var groupId: String = ""
|
var groupId: String = ""
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
@ -72,6 +76,43 @@ final class GroupChatViewModel {
|
||||||
cache[member.user_id] = member.userIcon
|
cache[member.user_id] = member.userIcon
|
||||||
}
|
}
|
||||||
avatarCache = cache
|
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 {
|
func getUserAvatar(id: String) -> UIImage {
|
||||||
|
|
@ -81,7 +122,7 @@ final class GroupChatViewModel {
|
||||||
avatarCache[id] = image
|
avatarCache[id] = image
|
||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
return UIImage(named: "GroupIcon1") ?? UIImage()
|
return UIImage(named: "UserIcon/1") ?? UIImage()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserNickName(id: String) -> String {
|
func getUserNickName(id: String) -> String {
|
||||||
|
|
@ -141,6 +182,8 @@ final class GroupChatViewModel {
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.messagesSubject.accept(items)
|
self.messagesSubject.accept(items)
|
||||||
|
// 进入会话后清除未读
|
||||||
|
self.markAsRead()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onFailure: { code, msg in
|
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) {
|
func appendLocalMessage(_ item: ChatSectionItem) {
|
||||||
var items = messagesSubject.value
|
var items = messagesSubject.value
|
||||||
|
|
@ -272,10 +323,12 @@ final class GroupChatViewModel {
|
||||||
imageW = 0; imageH = 0
|
imageW = 0; imageH = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sendID = msg.sendID ?? ""
|
||||||
return ChatMessage(
|
return ChatMessage(
|
||||||
id: msg.clientMsgID ?? UUID().uuidString,
|
id: msg.clientMsgID ?? UUID().uuidString,
|
||||||
isSelf: isSelf,
|
isSelf: isSelf,
|
||||||
avatar: getUserAvatar(id: msg.sendID ?? ""),
|
senderId: sendID,
|
||||||
|
avatar: getUserAvatar(id: sendID),
|
||||||
senderName: msg.senderNickname ?? "",
|
senderName: msg.senderNickname ?? "",
|
||||||
content: content,
|
content: content,
|
||||||
voiceUrl: voiceUrl,
|
voiceUrl: voiceUrl,
|
||||||
|
|
@ -322,19 +375,27 @@ final class GroupChatViewModel {
|
||||||
private func parseNotification(_ elem: OIMNotificationElem, contentType: Int) -> NSAttributedString? {
|
private func parseNotification(_ elem: OIMNotificationElem, contentType: Int) -> NSAttributedString? {
|
||||||
guard let data = elem.detail?.data(using: .utf8),
|
guard let data = elem.detail?.data(using: .utf8),
|
||||||
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||||
let group = json["group"] as? [String: Any],
|
let group = json["group"] as? [String: Any] else { return nil }
|
||||||
let opUser = json["opUser"] as? [String: Any] else { return nil }
|
|
||||||
|
|
||||||
switch contentType {
|
switch contentType {
|
||||||
case 1501, 1510:
|
case 1501:
|
||||||
// 群创建通知
|
// 群创建通知(admin 创建)
|
||||||
let ownerID = group["ownerUserID"] as? String ?? ""
|
guard let opUser = json["opUser"] as? [String: Any] else { return nil }
|
||||||
let ownerNickName = getUserNickName(id: ownerID)
|
let groupID = opUser["groupID"] as? String ?? ""
|
||||||
let text = ownerID == AppContextManager.shared.userId ? "圈子已经创建" : "\(ownerNickName) 创建了圈子"
|
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)
|
return NSAttributedString(string: text)
|
||||||
|
|
||||||
case 1520:
|
case 1520:
|
||||||
// 群名称改变通知
|
// 群名称改变通知
|
||||||
|
guard let opUser = json["opUser"] as? [String: Any] else { return nil }
|
||||||
let opUserID = opUser["userID"] as? String ?? ""
|
let opUserID = opUser["userID"] as? String ?? ""
|
||||||
let opNickName = getUserNickName(id: opUserID)
|
let opNickName = getUserNickName(id: opUserID)
|
||||||
let newName = group["groupName"] as? String ?? ""
|
let newName = group["groupName"] as? String ?? ""
|
||||||
|
|
@ -343,7 +404,6 @@ final class GroupChatViewModel {
|
||||||
let tip = "\(opNickName) 将群名称修改为 "
|
let tip = "\(opNickName) 将群名称修改为 "
|
||||||
let result = NSMutableAttributedString(string: tip + newName)
|
let result = NSMutableAttributedString(string: tip + newName)
|
||||||
result.addAttribute(.font, value: UIFont.systemFont(ofSize: 12), range: NSRange(location: 0, length: result.length))
|
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)
|
let nameRange = NSRange(location: tip.count, length: newName.utf16.count)
|
||||||
result.addAttribute(.foregroundColor, value: UIColor(hexStr: "#16B3FF"), range: nameRange)
|
result.addAttribute(.foregroundColor, value: UIColor(hexStr: "#16B3FF"), range: nameRange)
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -85,10 +85,22 @@ class GroupSettingVC: BaseViewController {
|
||||||
AppRouter.push(Route.inviteJoin, userInfo: ["groupInfo": model.toJSON()])
|
AppRouter.push(Route.inviteJoin, userInfo: ["groupInfo": model.toJSON()])
|
||||||
}).disposed(by: disposeBag)
|
}).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
|
rootView.dismissGroupView.rx.tapGesture.subscribe(onNext: { _ in
|
||||||
VerificationPopView.show {
|
VerificationPopView.show {
|
||||||
|
self.requestDismissGroup()
|
||||||
}
|
}
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
|
@ -165,6 +177,16 @@ class GroupSettingVC: BaseViewController {
|
||||||
}.disposed(by: disposeBag)
|
}.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
|
// MARK: - Init
|
||||||
init(groupId: String) {
|
init(groupId: String) {
|
||||||
self.viewModel = GroupSettingViewModel(groupId: groupId)
|
self.viewModel = GroupSettingViewModel(groupId: groupId)
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,9 @@ final class GroupViewController: BaseViewController {
|
||||||
guard !hasSetupIMListeners else { return }
|
guard !hasSetupIMListeners else { return }
|
||||||
hasSetupIMListeners = true
|
hasSetupIMListeners = true
|
||||||
|
|
||||||
GroupIMService.shared.setConversationListener { [weak self] _ in
|
// 监听器回调已包含完整的会话列表,直接使用,避免重复 MJExtension 解析
|
||||||
GroupIMService.shared.getConversationList { conversations in
|
GroupIMService.shared.setConversationListener { [weak self] conversations in
|
||||||
self?.viewModel.updateConversations(conversations)
|
self?.viewModel.updateConversations(conversations)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 群组同步完成(如刚创建/加入的群)后刷新群列表,解决业务接口先返回、SDK 后同步的时序问题
|
// 群组同步完成(如刚创建/加入的群)后刷新群列表,解决业务接口先返回、SDK 后同步的时序问题
|
||||||
|
|
@ -105,6 +104,10 @@ final class GroupViewController: BaseViewController {
|
||||||
.observe(on: MainScheduler.asyncInstance)
|
.observe(on: MainScheduler.asyncInstance)
|
||||||
.bind(to: rootView.joinedTableView.rx.items(dataSource: joinedDataSource))
|
.bind(to: rootView.joinedTableView.rx.items(dataSource: joinedDataSource))
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
rootView.hotGroupsCollectionView.rx.modelSelected(GroupInfoModel.self)
|
||||||
|
.subscribe(viewModel.hotGroupCellAction.inputs)
|
||||||
|
.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reactiveAction() {
|
private func reactiveAction() {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import RxSwift
|
||||||
import RxCocoa
|
import RxCocoa
|
||||||
import RxDataSources
|
import RxDataSources
|
||||||
import OpenIMSDK
|
import OpenIMSDK
|
||||||
|
import ObjectMapper
|
||||||
|
|
||||||
typealias HotGroupListSectionModel = SectionModel<String, GroupInfoModel>
|
typealias HotGroupListSectionModel = SectionModel<String, GroupInfoModel>
|
||||||
typealias CircleListSectionModel = SectionModel<String, GroupCellData>
|
typealias CircleListSectionModel = SectionModel<String, GroupCellData>
|
||||||
|
|
@ -52,6 +53,13 @@ final class GroupViewModel {
|
||||||
hotGroupSectionedItems.onNext(list.mapSection())
|
hotGroupSectionedItems.onNext(list.mapSection())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy var hotGroupCellAction: Action<GroupInfoModel, Void> = { this in
|
||||||
|
return Action { model in
|
||||||
|
AppRouter.push(Route.groupInfo, userInfo: ["groupInfo": model.toJSON()])
|
||||||
|
return .empty()
|
||||||
|
}
|
||||||
|
}(self)
|
||||||
|
|
||||||
/// 加载群列表(全量刷新)
|
/// 加载群列表(全量刷新)
|
||||||
func loadIMGroups(_ groups: [OIMGroupInfo], conversations: [OIMConversationInfo] = []) {
|
func loadIMGroups(_ groups: [OIMGroupInfo], conversations: [OIMConversationInfo] = []) {
|
||||||
if !groups.isEmpty { cachedGroups = groups }
|
if !groups.isEmpty { cachedGroups = groups }
|
||||||
|
|
|
||||||
|
|
@ -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<ReviewMemberListSectionModel> = {
|
||||||
|
return RxTableViewSectionedReloadDataSource<ReviewMemberListSectionModel>(
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
//
|
||||||
|
// RemoveMemberViewModel.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
// Created by 八条 on 2026/6/12.
|
||||||
|
//
|
||||||
|
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
import RxDataSources
|
||||||
|
import ObjectMapper
|
||||||
|
|
||||||
|
typealias RemoveMemberListSectionModel = SectionModel<String, GroupMemberModel>
|
||||||
|
|
||||||
|
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<GroupMemberModel, Void> = { 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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,13 @@ class ReviewMemberListVC: BaseViewController {
|
||||||
viewModel.output.sectionedItems
|
viewModel.output.sectionedItems
|
||||||
.bind(to: rootView.tableView.rx.items(dataSource: dataSource))
|
.bind(to: rootView.tableView.rx.items(dataSource: dataSource))
|
||||||
.disposed(by: disposeBag)
|
.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
|
// MARK: - UITableViewDataSource
|
||||||
|
|
@ -43,6 +50,15 @@ class ReviewMemberListVC: BaseViewController {
|
||||||
configureCell: { (_, tableView, indexPath, model) in
|
configureCell: { (_, tableView, indexPath, model) in
|
||||||
let cell: ReviewMemberCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: ReviewMemberCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.configure(model)
|
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
|
return cell
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
@ -52,7 +68,17 @@ class ReviewMemberListVC: BaseViewController {
|
||||||
DLToast.showLoading()
|
DLToast.showLoading()
|
||||||
GroupService.reviewlist(requestData: ["group_key": viewModel.groupId]).subscribe { response in
|
GroupService.reviewlist(requestData: ["group_key": viewModel.groupId]).subscribe { response in
|
||||||
DLToast.dismiss()
|
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)
|
}.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ struct ReviewMemberListViewModel {
|
||||||
private var disposeBag = DisposeBag()
|
private var disposeBag = DisposeBag()
|
||||||
private let sectionedItems = PublishSubject<[ReviewMemberListSectionModel]>()
|
private let sectionedItems = PublishSubject<[ReviewMemberListSectionModel]>()
|
||||||
|
|
||||||
|
func loadData(_ list: [GroupMemberModel]) {
|
||||||
|
sectionedItems.onNext(list.mapSection())
|
||||||
|
}
|
||||||
|
|
||||||
init(groupId: String) {
|
init(groupId: String) {
|
||||||
self.groupId = groupId
|
self.groupId = groupId
|
||||||
output = Output(
|
output = Output(
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import RxCocoa
|
||||||
class ReviewMemberListView: UIView {
|
class ReviewMemberListView: UIView {
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
var disposeBag = DisposeBag()
|
||||||
|
/// 单行高度
|
||||||
|
static let rowHeight: CGFloat = 85
|
||||||
|
|
||||||
private func setupRx() {
|
private func setupRx() {
|
||||||
backBtn.rx.tap.subscribe(onNext: { _ in
|
backBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
|
|
@ -30,7 +32,7 @@ class ReviewMemberListView: UIView {
|
||||||
|
|
||||||
navBgView.layoutChain
|
navBgView.layoutChain
|
||||||
.edges(excludingEdge: .bottom)
|
.edges(excludingEdge: .bottom)
|
||||||
.heightToWidth(160/375)
|
.height(kNaviHeight)
|
||||||
|
|
||||||
navBarView.layoutChain
|
navBarView.layoutChain
|
||||||
.edges(excludingEdge: .bottom)
|
.edges(excludingEdge: .bottom)
|
||||||
|
|
@ -38,7 +40,6 @@ class ReviewMemberListView: UIView {
|
||||||
|
|
||||||
navTitleLabel.layoutChain
|
navTitleLabel.layoutChain
|
||||||
.top(kStatusBarHeight + 12)
|
.top(kStatusBarHeight + 12)
|
||||||
.centerY(backBtn)
|
|
||||||
.centerX()
|
.centerX()
|
||||||
|
|
||||||
backBtn.layoutChain
|
backBtn.layoutChain
|
||||||
|
|
@ -48,14 +49,27 @@ class ReviewMemberListView: UIView {
|
||||||
.height(24)
|
.height(24)
|
||||||
|
|
||||||
cornerBgView.layoutChain
|
cornerBgView.layoutChain
|
||||||
.edges(excludingEdge: .bottom)
|
.topToBottomOfView(navBarView, offset: 16)
|
||||||
.bottom(kSafeBottomMargin + 10, relation: .greaterThanOrEqual)
|
.edgesHorzontal(15)
|
||||||
|
|
||||||
tableView.layoutChain
|
tableView.layoutChain
|
||||||
.edges(excludingEdge: .bottom)
|
.topToBottomOfView(navBarView, offset: 16)
|
||||||
.bottom(kSafeBottomMargin + 10, relation: .greaterThanOrEqual)
|
.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 = {
|
lazy var navBgView: UIImageView = {
|
||||||
let iv = UIImageView()
|
let iv = UIImageView()
|
||||||
iv.image = UIImage(named: "Common/navBar_bg_2")
|
iv.image = UIImage(named: "Common/navBar_bg_2")
|
||||||
|
|
@ -96,7 +110,8 @@ class ReviewMemberListView: UIView {
|
||||||
let tableView = UITableView(frame: .zero, style: .plain)
|
let tableView = UITableView(frame: .zero, style: .plain)
|
||||||
tableView.backgroundColor = .clear
|
tableView.backgroundColor = .clear
|
||||||
tableView.separatorStyle = .none
|
tableView.separatorStyle = .none
|
||||||
tableView.estimatedRowHeight = 77
|
tableView.estimatedRowHeight = 85
|
||||||
|
tableView.isScrollEnabled = false
|
||||||
tableView.register(ReviewMemberCell.self)
|
tableView.register(ReviewMemberCell.self)
|
||||||
return tableView
|
return tableView
|
||||||
}()
|
}()
|
||||||
|
|
@ -118,8 +133,11 @@ class ReviewMemberListView: UIView {
|
||||||
// MARK: - ReviewMemberCell
|
// MARK: - ReviewMemberCell
|
||||||
class ReviewMemberCell: UITableViewCell {
|
class ReviewMemberCell: UITableViewCell {
|
||||||
|
|
||||||
func configure(_ model: GroupMemberModel) {
|
var disposeBag = DisposeBag()
|
||||||
|
|
||||||
|
func configure(_ model: GroupMemberModel) {
|
||||||
|
avaterImgView.image = model.userIcon
|
||||||
|
nameLab.text = model.nick_name
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(style: CellStyle, reuseIdentifier: String?) {
|
override init(style: CellStyle, reuseIdentifier: String?) {
|
||||||
|
|
@ -135,6 +153,7 @@ class ReviewMemberCell: UITableViewCell {
|
||||||
bgView.addSubview(nameLab)
|
bgView.addSubview(nameLab)
|
||||||
bgView.addSubview(agreeBtn)
|
bgView.addSubview(agreeBtn)
|
||||||
bgView.addSubview(refuseBtn)
|
bgView.addSubview(refuseBtn)
|
||||||
|
bgView.addSubview(lineView)
|
||||||
|
|
||||||
bgView.layoutChain
|
bgView.layoutChain
|
||||||
.edgesHorzontal(15)
|
.edgesHorzontal(15)
|
||||||
|
|
@ -152,15 +171,20 @@ class ReviewMemberCell: UITableViewCell {
|
||||||
|
|
||||||
agreeBtn.layoutChain
|
agreeBtn.layoutChain
|
||||||
.centerY()
|
.centerY()
|
||||||
.right(15)
|
.right(5)
|
||||||
.width(34)
|
.width(50)
|
||||||
.height(15)
|
.height(30)
|
||||||
|
|
||||||
refuseBtn.layoutChain
|
refuseBtn.layoutChain
|
||||||
.centerY()
|
.centerY()
|
||||||
.rightToLeftOfView(agreeBtn, offset: 8)
|
.rightToLeftOfView(agreeBtn, offset: -8)
|
||||||
.width(34)
|
.width(50)
|
||||||
.height(15)
|
.height(30)
|
||||||
|
|
||||||
|
lineView.layoutChain
|
||||||
|
.height(0.5)
|
||||||
|
.edgesHorzontal(15)
|
||||||
|
.bottom()
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy var bgView: UIView = {
|
lazy var bgView: UIView = {
|
||||||
|
|
@ -187,9 +211,9 @@ class ReviewMemberCell: UITableViewCell {
|
||||||
let btn = UIButton(type: .custom)
|
let btn = UIButton(type: .custom)
|
||||||
btn.setTitle("同意", for: .normal)
|
btn.setTitle("同意", for: .normal)
|
||||||
btn.setTitleColor(.white, 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.setBackgroundColor(UIColor(hexStr: "#16B3FF"), for: .normal)
|
||||||
btn.cornerRadius = 7.5
|
btn.cornerRadius = 15
|
||||||
return btn
|
return btn
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -197,14 +221,20 @@ class ReviewMemberCell: UITableViewCell {
|
||||||
let btn = UIButton(type: .custom)
|
let btn = UIButton(type: .custom)
|
||||||
btn.setTitle("拒绝", for: .normal)
|
btn.setTitle("拒绝", for: .normal)
|
||||||
btn.setTitleColor(UIColor(hexStr: "#16B3FF"), 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.setBackgroundColor(.white, for: .normal)
|
||||||
btn.borderWidth = 0.5
|
btn.borderWidth = 0.5
|
||||||
btn.borderColor = UIColor(hexStr: "#16B3FF")
|
btn.borderColor = UIColor(hexStr: "#16B3FF")
|
||||||
btn.cornerRadius = 7.5
|
btn.cornerRadius = 15
|
||||||
return btn
|
return btn
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
lazy var lineView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = ThemeManager.shared.color.lineColor
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
@ -219,4 +249,9 @@ class ReviewMemberCell: UITableViewCell {
|
||||||
|
|
||||||
// Configure the view for the selected state
|
// Configure the view for the selected state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
super.prepareForReuse()
|
||||||
|
disposeBag = DisposeBag()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,25 +126,31 @@ class HomeViewController: BaseViewController {
|
||||||
Defaults[\.loginToken] = model.token
|
Defaults[\.loginToken] = model.token
|
||||||
AppContextManager.shared.systemConfig = model.config
|
AppContextManager.shared.systemConfig = model.config
|
||||||
self.getUserIMToken()
|
self.getUserIMToken()
|
||||||
self.requestUserInfo()
|
// 先更新用户信息(含头像),再拉群列表同步地图标注,避免头像旧
|
||||||
self.requestGroupInfo()
|
self.requestUserInfo { [weak self] in
|
||||||
|
self?.requestGroupInfo()
|
||||||
|
}
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取用户IM Token
|
/// 获取用户IM Token
|
||||||
func getUserIMToken() {
|
func getUserIMToken() {
|
||||||
|
DLToast.showLoading()
|
||||||
UserService.imToken().subscribe(onNext: { response in
|
UserService.imToken().subscribe(onNext: { response in
|
||||||
guard let data = response.data, let token = data["token"] as? String else { return }
|
guard let data = response.data, let token = data["token"] as? String else { return }
|
||||||
AppContextManager.shared.imToken = token
|
AppContextManager.shared.imToken = token
|
||||||
GroupIMService.shared.login { _ in }
|
GroupIMService.shared.login { _ in
|
||||||
|
DLToast.dismiss()
|
||||||
|
}
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func requestUserInfo() {
|
private func requestUserInfo(completion: (() -> Void)? = nil) {
|
||||||
UserService.userInfo().subscribe { response in
|
UserService.userInfo().subscribe { response in
|
||||||
guard let model = response.model else { return }
|
guard let model = response.model else { return }
|
||||||
AppContextManager.shared.saveAccount(model)
|
AppContextManager.shared.saveAccount(model)
|
||||||
self.rootView.avatarImgView.image = model.userIcon
|
self.rootView.avatarImgView.image = model.userIcon
|
||||||
|
completion?()
|
||||||
}.disposed(by: disposeBag)
|
}.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,7 @@ class LaunchViewController: BaseViewController {
|
||||||
guard let model = response.model else { return }
|
guard let model = response.model else { return }
|
||||||
Defaults[\.loginToken] = model.token
|
Defaults[\.loginToken] = model.token
|
||||||
AppContextManager.shared.systemConfig = model.config
|
AppContextManager.shared.systemConfig = model.config
|
||||||
// 保存用户数据
|
self.getUserIMToken()
|
||||||
// AppContextManager.shared.saveAccount(model)
|
|
||||||
}, onError: { [weak self] (error) in
|
}, onError: { [weak self] (error) in
|
||||||
DLAlert.show(title: error.localizedDescription,
|
DLAlert.show(title: error.localizedDescription,
|
||||||
defaultTitle: "重试") { [weak self] in
|
defaultTitle: "重试") { [weak self] in
|
||||||
|
|
@ -82,6 +81,16 @@ class LaunchViewController: BaseViewController {
|
||||||
}).disposed(by: disposeBag)
|
}).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
|
// MARK: - Init
|
||||||
init() {
|
init() {
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -44,6 +44,8 @@ class MineViewModel {
|
||||||
AppRouter.push(Route.privacyPolicy)
|
AppRouter.push(Route.privacyPolicy)
|
||||||
case "在线客服":
|
case "在线客服":
|
||||||
this.requestWechatService()
|
this.requestWechatService()
|
||||||
|
case "权限检测":
|
||||||
|
AppRouter.push(Route.checkPermission)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ class ScanVC: BaseViewController {
|
||||||
private func requestJoinGroup(code: String) {
|
private func requestJoinGroup(code: String) {
|
||||||
DLToast.showLoading()
|
DLToast.showLoading()
|
||||||
GroupService.operate(opType: "join", requestData: ["share_code" : code]).subscribe(onNext: { response in
|
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)
|
NotificationCenter.default.post(name: .RefreshGroupInfoNotification, object: nil)
|
||||||
AppRouter.shared.popOrDismiss()
|
AppRouter.shared.popOrDismiss()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -370,7 +370,7 @@ class FullscreenWebView: WKWebView {
|
||||||
if self.isHome {
|
if self.isHome {
|
||||||
self.navTitle = self.webView.title ?? ""
|
self.navTitle = self.webView.title ?? ""
|
||||||
} else {
|
} else {
|
||||||
self.navTitle = self.webView.title ?? ""//self.webView.title == "物联物美" ? "便民商圈" : self.webView.title ?? ""
|
self.navTitle = self.webView.title ?? ""
|
||||||
}
|
}
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,10 +99,26 @@ struct GroupService {
|
||||||
/// 审核列表
|
/// 审核列表
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - requestData:group_key
|
/// - requestData:group_key
|
||||||
static func reviewlist(requestData: [String: Any]) -> Observable<GroupInfoResponse> {
|
static func reviewlist(requestData: [String: Any]) -> Observable<ReviewMemberListResponse> {
|
||||||
let api = GroupAPI.operate(opType: "reviewlist", requestData: requestData).multiTarget
|
let api = GroupAPI.operate(opType: "reviewlist", requestData: requestData).multiTarget
|
||||||
return APIProvider.request(token: api)
|
return APIProvider.request(token: api)
|
||||||
.map(GroupInfoResponse.self)
|
.map(ReviewMemberListResponse.self)
|
||||||
|
.asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 审核成员
|
||||||
|
static func reviewMember(requestData: [String: Any]) -> Observable<ResponseModel> {
|
||||||
|
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<ResponseModel> {
|
||||||
|
let api = GroupAPI.operate(opType: "kick", requestData: requestData).multiTarget
|
||||||
|
return APIProvider.request(token: api)
|
||||||
|
.map(ResponseModel.self)
|
||||||
.asObservable()
|
.asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,4 +131,12 @@ struct GroupService {
|
||||||
.map(GroupInfoResponse.self)
|
.map(GroupInfoResponse.self)
|
||||||
.asObservable()
|
.asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 解散群聊
|
||||||
|
static func dismiss(requestData: [String: Any]) -> Observable<GroupInfoResponse> {
|
||||||
|
let api = GroupAPI.operate(opType: "dismiss", requestData: requestData).multiTarget
|
||||||
|
return APIProvider.request(token: api)
|
||||||
|
.map(GroupInfoResponse.self)
|
||||||
|
.asObservable()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||