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