- 清除vip、隐藏开关
|
|
@ -101,8 +101,6 @@
|
|||
305A76E02FCA8C7000227D26 /* GroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A76232FCA8C7000227D26 /* GroupView.swift */; };
|
||||
305A76E12FCA8C7000227D26 /* GroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A76242FCA8C7000227D26 /* GroupViewController.swift */; };
|
||||
305A76E22FCA8C7000227D26 /* GroupViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A76252FCA8C7000227D26 /* GroupViewModel.swift */; };
|
||||
305A76E32FCA8C7000227D26 /* GroupMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A76272FCA8C7000227D26 /* GroupMemberView.swift */; };
|
||||
305A76E42FCA8C7000227D26 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A76282FCA8C7000227D26 /* HomeView.swift */; };
|
||||
305A76E52FCA8C7000227D26 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A76292FCA8C7000227D26 /* HomeViewController.swift */; };
|
||||
305A76E62FCA8C7000227D26 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A762B2FCA8C7000227D26 /* LaunchViewController.swift */; };
|
||||
305A76E72FCA8C7000227D26 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A762D2FCA8C7000227D26 /* LoginView.swift */; };
|
||||
|
|
@ -167,7 +165,6 @@
|
|||
3062E8BE2FCEBD0E00CEF511 /* GroupIconListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3062E8BD2FCEBD0E00CEF511 /* GroupIconListVC.swift */; };
|
||||
3062E8C02FCED7BB00CEF511 /* GroupIconListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3062E8BF2FCED7BB00CEF511 /* GroupIconListView.swift */; };
|
||||
3062E8C22FCFB86800CEF511 /* CreateGroupViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3062E8C12FCFB86800CEF511 /* CreateGroupViewModel.swift */; };
|
||||
3062E8C42FCFC90F00CEF511 /* CreateGroupVipPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3062E8C32FCFC90F00CEF511 /* CreateGroupVipPopView.swift */; };
|
||||
307073E12FD15F50004C37CC /* GroupIMService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307073E02FD15F50004C37CC /* GroupIMService.swift */; };
|
||||
307073E52FD18A20004C37CC /* GroupChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307073E32FD18A20004C37CC /* GroupChatView.swift */; };
|
||||
307073E62FD18A20004C37CC /* GroupChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307073E22FD18A20004C37CC /* GroupChatVC.swift */; };
|
||||
|
|
@ -188,13 +185,10 @@
|
|||
30A87A622FEE724D0095E7C6 /* CreateBubbleTiemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A612FEE724D0095E7C6 /* CreateBubbleTiemView.swift */; };
|
||||
30A87A642FEE75520095E7C6 /* CreateBubbleTipsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A632FEE75520095E7C6 /* CreateBubbleTipsView.swift */; };
|
||||
30A87A662FEE843E0095E7C6 /* CreateBubbleDoneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A652FEE843E0095E7C6 /* CreateBubbleDoneView.swift */; };
|
||||
30A87A682FEE86560095E7C6 /* CreateBubblePopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A672FEE86560095E7C6 /* CreateBubblePopView.swift */; };
|
||||
30A87A6B2FEF5B950095E7C6 /* SearchLocationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A6A2FEF5B950095E7C6 /* SearchLocationView.swift */; };
|
||||
30A87A6D2FEF5BA10095E7C6 /* SearchLocationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A6C2FEF5BA10095E7C6 /* SearchLocationVC.swift */; };
|
||||
30A87A6F2FEF7BE40095E7C6 /* SearchLocationResultVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A6E2FEF7BE40095E7C6 /* SearchLocationResultVC.swift */; };
|
||||
30A87A712FEF7BED0095E7C6 /* SearchLocationResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A702FEF7BED0095E7C6 /* SearchLocationResultView.swift */; };
|
||||
30ACD54C2FF6336000174861 /* PopupWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30ACD54B2FF6336000174861 /* PopupWindow.swift */; };
|
||||
30ACD67B2FF7B36A00174861 /* PopupQueueManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30ACD67A2FF7B36A00174861 /* PopupQueueManager.swift */; };
|
||||
30B2D0B12FEB111100CA0001 /* PhotoPickerStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B2D0B02FEB111100CA0001 /* PhotoPickerStore.swift */; };
|
||||
30B2D0B32FEB111100CA0001 /* PhotoPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B2D0B22FEB111100CA0001 /* PhotoPickerView.swift */; };
|
||||
30B2D0B52FEB111100CA0001 /* PhotoPickerAlbumView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B2D0B42FEB111100CA0001 /* PhotoPickerAlbumView.swift */; };
|
||||
|
|
@ -227,9 +221,7 @@
|
|||
30C4C01D2FDBF557009215C1 /* RemoveMemberViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C01C2FDBF557009215C1 /* RemoveMemberViewModel.swift */; };
|
||||
30C4C0202FDC0EC5009215C1 /* GroupInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C01F2FDC0EC5009215C1 /* GroupInfoView.swift */; };
|
||||
30C4C0222FDC0ED3009215C1 /* GroupInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C0212FDC0ED3009215C1 /* GroupInfoVC.swift */; };
|
||||
30C6666C2FFB7C4000E62B25 /* IAPManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C6666B2FFB7C4000E62B25 /* IAPManager.swift */; };
|
||||
30C6679E2FFB7FEF00E62B25 /* SharePopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C6679C2FFB7FEF00E62B25 /* SharePopView.swift */; };
|
||||
30CCDE4E2FE26CEA00F5214A /* PayResultPopVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCDE4D2FE26CEA00F5214A /* PayResultPopVC.swift */; };
|
||||
30CCDE512FE2785D00F5214A /* SignInVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCDE502FE2785D00F5214A /* SignInVC.swift */; };
|
||||
30CCDE532FE2786600F5214A /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCDE522FE2786600F5214A /* SignInView.swift */; };
|
||||
30CCDE552FE2903100F5214A /* SignInModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCDE542FE2903100F5214A /* SignInModel.swift */; };
|
||||
|
|
@ -237,9 +229,6 @@
|
|||
30CCDE5A2FE39F9D00F5214A /* SOSViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCDE592FE39F9D00F5214A /* SOSViewController.swift */; };
|
||||
30CCDE5C2FE3A1A800F5214A /* SOSPracticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCDE5B2FE3A1A800F5214A /* SOSPracticeView.swift */; };
|
||||
30D74AAB2FE8C7700050EB2C /* GPSSignalHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AAA2FE8C7700050EB2C /* GPSSignalHelper.swift */; };
|
||||
30D74AAE2FEA13E00050EB2C /* ScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AAD2FEA13E00050EB2C /* ScheduleView.swift */; };
|
||||
30D74AB02FEA13ED0050EB2C /* ScheduleVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AAF2FEA13ED0050EB2C /* ScheduleVC.swift */; };
|
||||
30D74AB22FEA1D5D0050EB2C /* ScheduleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AB12FEA1D5D0050EB2C /* ScheduleViewModel.swift */; };
|
||||
30D74AB42FEA25B90050EB2C /* ViewedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AB32FEA25B90050EB2C /* ViewedModel.swift */; };
|
||||
30D74AB62FEA34FF0050EB2C /* ItineraryAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AB52FEA34FF0050EB2C /* ItineraryAPI.swift */; };
|
||||
30D74AB82FEA36A50050EB2C /* ItineraryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AB72FEA36A50050EB2C /* ItineraryService.swift */; };
|
||||
|
|
@ -249,13 +238,9 @@
|
|||
30D74AC12FEA6EEF0050EB2C /* CreateSchedulePopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AC02FEA6EEF0050EB2C /* CreateSchedulePopView.swift */; };
|
||||
30D74D1F2FEBB09B0050EB2C /* CreateScheduleVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74D1E2FEBB09B0050EB2C /* CreateScheduleVM.swift */; };
|
||||
30D87CDB2FDFA9EE00E958FD /* MQTTService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D87CDA2FDFA9EE00E958FD /* MQTTService.swift */; };
|
||||
30D87CDD2FDFF07500E958FD /* InteractionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D87CDC2FDFF07500E958FD /* InteractionView.swift */; };
|
||||
30D87CDF2FDFF1A100E958FD /* QuickMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */; };
|
||||
30D87D042FE1336300E958FD /* NavigationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D87D012FE1336300E958FD /* NavigationVC.swift */; };
|
||||
30D87D052FE1336300E958FD /* NavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D87D022FE1336300E958FD /* NavigationView.swift */; };
|
||||
30D891F52FE22E0600E958FD /* OrderAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D891F42FE22E0600E958FD /* OrderAPI.swift */; };
|
||||
30D891F72FE22E6E00E958FD /* OrderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D891F62FE22E6E00E958FD /* OrderService.swift */; };
|
||||
30DA36BD2FECC5AB008D5A2C /* CreateScheduleVipPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DA36BC2FECC5AB008D5A2C /* CreateScheduleVipPopView.swift */; };
|
||||
30DC185A2FD11E7A0041DCD1 /* WebOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18562FD11E7A0041DCD1 /* WebOperations.swift */; };
|
||||
30DC185B2FD11E7A0041DCD1 /* NavigationTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18552FD11E7A0041DCD1 /* NavigationTitleView.swift */; };
|
||||
30DC185C2FD11E7A0041DCD1 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18572FD11E7A0041DCD1 /* WebViewController.swift */; };
|
||||
|
|
@ -366,11 +351,13 @@
|
|||
55B26001304B000100786601 /* VersionUpgradePopVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B26000304B000100786600 /* VersionUpgradePopVC.swift */; };
|
||||
55B261010000000100786601 /* PrivacySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B261000000000100786600 /* PrivacySettingsViewController.swift */; };
|
||||
55BF752D2FFE53F70055DA57 /* LocationPermissionPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BF752C2FFE53F70055DA57 /* LocationPermissionPopView.swift */; };
|
||||
55BF75352FFF91690055DA57 /* InteractionEmojiCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BF75342FFF91690055DA57 /* InteractionEmojiCell.swift */; };
|
||||
55C1A0113034101000786011 /* PhoneStatusCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55C1A0103034101000786010 /* PhoneStatusCollector.swift */; };
|
||||
55C1A0213034102000786021 /* PrivilegeDeniedOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55C1A0203034102000786020 /* PrivilegeDeniedOverlay.swift */; };
|
||||
55C219C13024C00100784731 /* SearchLocationHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55C219C13024C00100784730 /* SearchLocationHeaderView.swift */; };
|
||||
55C4C0242FDB91C8009215C2 /* CheckPermissionGuideVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55C4C0232FDB91C8009215C2 /* CheckPermissionGuideVC.swift */; };
|
||||
55CA150E3058E55E000F1189 /* ScheduleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CA150B3058E55E000F1189 /* ScheduleViewModel.swift */; };
|
||||
55CA15103058EBE5000F1189 /* InteractionEmojiCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CA150F3058EBE5000F1189 /* InteractionEmojiCell.swift */; };
|
||||
55CA15123058EC23000F1189 /* InteractionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CA15113058EC23000F1189 /* InteractionView.swift */; };
|
||||
60BEE25E61F3DE24976A7A47 /* ManagedSettingsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FE44DF1C46BDA7B98BC7859 /* ManagedSettingsUI.framework */; };
|
||||
68F0D48EB76039050533197C /* AppRestrictShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB29D0618C4BE76F2CA5261 /* AppRestrictShared.swift */; };
|
||||
6D90E8FFE4D597E888E6A224 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E326B06736FE04DD7A5E96D /* UIKit.framework */; };
|
||||
|
|
@ -539,8 +526,6 @@
|
|||
305A76232FCA8C7000227D26 /* GroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupView.swift; sourceTree = "<group>"; };
|
||||
305A76242FCA8C7000227D26 /* GroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupViewController.swift; sourceTree = "<group>"; };
|
||||
305A76252FCA8C7000227D26 /* GroupViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupViewModel.swift; sourceTree = "<group>"; };
|
||||
305A76272FCA8C7000227D26 /* GroupMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberView.swift; sourceTree = "<group>"; };
|
||||
305A76282FCA8C7000227D26 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
||||
305A76292FCA8C7000227D26 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = "<group>"; };
|
||||
305A762B2FCA8C7000227D26 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = "<group>"; };
|
||||
305A762D2FCA8C7000227D26 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -609,7 +594,6 @@
|
|||
3062E8BD2FCEBD0E00CEF511 /* GroupIconListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupIconListVC.swift; sourceTree = "<group>"; };
|
||||
3062E8BF2FCED7BB00CEF511 /* GroupIconListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupIconListView.swift; sourceTree = "<group>"; };
|
||||
3062E8C12FCFB86800CEF511 /* CreateGroupViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupViewModel.swift; sourceTree = "<group>"; };
|
||||
3062E8C32FCFC90F00CEF511 /* CreateGroupVipPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupVipPopView.swift; sourceTree = "<group>"; };
|
||||
307073E02FD15F50004C37CC /* GroupIMService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupIMService.swift; sourceTree = "<group>"; };
|
||||
307073E22FD18A20004C37CC /* GroupChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatVC.swift; sourceTree = "<group>"; };
|
||||
307073E32FD18A20004C37CC /* GroupChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -630,13 +614,10 @@
|
|||
30A87A612FEE724D0095E7C6 /* CreateBubbleTiemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubbleTiemView.swift; sourceTree = "<group>"; };
|
||||
30A87A632FEE75520095E7C6 /* CreateBubbleTipsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubbleTipsView.swift; sourceTree = "<group>"; };
|
||||
30A87A652FEE843E0095E7C6 /* CreateBubbleDoneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubbleDoneView.swift; sourceTree = "<group>"; };
|
||||
30A87A672FEE86560095E7C6 /* CreateBubblePopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubblePopView.swift; sourceTree = "<group>"; };
|
||||
30A87A6A2FEF5B950095E7C6 /* SearchLocationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationView.swift; sourceTree = "<group>"; };
|
||||
30A87A6C2FEF5BA10095E7C6 /* SearchLocationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationVC.swift; sourceTree = "<group>"; };
|
||||
30A87A6E2FEF7BE40095E7C6 /* SearchLocationResultVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationResultVC.swift; sourceTree = "<group>"; };
|
||||
30A87A702FEF7BED0095E7C6 /* SearchLocationResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationResultView.swift; sourceTree = "<group>"; };
|
||||
30ACD54B2FF6336000174861 /* PopupWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupWindow.swift; sourceTree = "<group>"; };
|
||||
30ACD67A2FF7B36A00174861 /* PopupQueueManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupQueueManager.swift; sourceTree = "<group>"; };
|
||||
30B2D0B02FEB111100CA0001 /* PhotoPickerStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPickerStore.swift; sourceTree = "<group>"; };
|
||||
30B2D0B22FEB111100CA0001 /* PhotoPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPickerView.swift; sourceTree = "<group>"; };
|
||||
30B2D0B42FEB111100CA0001 /* PhotoPickerAlbumView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPickerAlbumView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -670,9 +651,7 @@
|
|||
30C4C01C2FDBF557009215C1 /* RemoveMemberViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveMemberViewModel.swift; sourceTree = "<group>"; };
|
||||
30C4C01F2FDC0EC5009215C1 /* GroupInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInfoView.swift; sourceTree = "<group>"; };
|
||||
30C4C0212FDC0ED3009215C1 /* GroupInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInfoVC.swift; sourceTree = "<group>"; };
|
||||
30C6666B2FFB7C4000E62B25 /* IAPManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAPManager.swift; sourceTree = "<group>"; };
|
||||
30C6679C2FFB7FEF00E62B25 /* SharePopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharePopView.swift; sourceTree = "<group>"; };
|
||||
30CCDE4D2FE26CEA00F5214A /* PayResultPopVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayResultPopVC.swift; sourceTree = "<group>"; };
|
||||
30CCDE502FE2785D00F5214A /* SignInVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInVC.swift; sourceTree = "<group>"; };
|
||||
30CCDE522FE2786600F5214A /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = "<group>"; };
|
||||
30CCDE542FE2903100F5214A /* SignInModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInModel.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -680,9 +659,6 @@
|
|||
30CCDE592FE39F9D00F5214A /* SOSViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOSViewController.swift; sourceTree = "<group>"; };
|
||||
30CCDE5B2FE3A1A800F5214A /* SOSPracticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOSPracticeView.swift; sourceTree = "<group>"; };
|
||||
30D74AAA2FE8C7700050EB2C /* GPSSignalHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPSSignalHelper.swift; sourceTree = "<group>"; };
|
||||
30D74AAD2FEA13E00050EB2C /* ScheduleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleView.swift; sourceTree = "<group>"; };
|
||||
30D74AAF2FEA13ED0050EB2C /* ScheduleVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleVC.swift; sourceTree = "<group>"; };
|
||||
30D74AB12FEA1D5D0050EB2C /* ScheduleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleViewModel.swift; sourceTree = "<group>"; };
|
||||
30D74AB32FEA25B90050EB2C /* ViewedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewedModel.swift; sourceTree = "<group>"; };
|
||||
30D74AB52FEA34FF0050EB2C /* ItineraryAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItineraryAPI.swift; sourceTree = "<group>"; };
|
||||
30D74AB72FEA36A50050EB2C /* ItineraryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItineraryService.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -692,13 +668,9 @@
|
|||
30D74AC02FEA6EEF0050EB2C /* CreateSchedulePopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSchedulePopView.swift; sourceTree = "<group>"; };
|
||||
30D74D1E2FEBB09B0050EB2C /* CreateScheduleVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateScheduleVM.swift; sourceTree = "<group>"; };
|
||||
30D87CDA2FDFA9EE00E958FD /* MQTTService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTService.swift; sourceTree = "<group>"; };
|
||||
30D87CDC2FDFF07500E958FD /* InteractionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionView.swift; sourceTree = "<group>"; };
|
||||
30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickMessageView.swift; sourceTree = "<group>"; };
|
||||
30D87D012FE1336300E958FD /* NavigationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationVC.swift; sourceTree = "<group>"; };
|
||||
30D87D022FE1336300E958FD /* NavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationView.swift; sourceTree = "<group>"; };
|
||||
30D891F42FE22E0600E958FD /* OrderAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderAPI.swift; sourceTree = "<group>"; };
|
||||
30D891F62FE22E6E00E958FD /* OrderService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderService.swift; sourceTree = "<group>"; };
|
||||
30DA36BC2FECC5AB008D5A2C /* CreateScheduleVipPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateScheduleVipPopView.swift; sourceTree = "<group>"; };
|
||||
30DC18552FD11E7A0041DCD1 /* NavigationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTitleView.swift; sourceTree = "<group>"; };
|
||||
30DC18562FD11E7A0041DCD1 /* WebOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebOperations.swift; sourceTree = "<group>"; };
|
||||
30DC18572FD11E7A0041DCD1 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -813,11 +785,13 @@
|
|||
55B26000304B000100786600 /* VersionUpgradePopVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpgradePopVC.swift; sourceTree = "<group>"; };
|
||||
55B261000000000100786600 /* PrivacySettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettingsViewController.swift; sourceTree = "<group>"; };
|
||||
55BF752C2FFE53F70055DA57 /* LocationPermissionPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPermissionPopView.swift; sourceTree = "<group>"; };
|
||||
55BF75342FFF91690055DA57 /* InteractionEmojiCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionEmojiCell.swift; sourceTree = "<group>"; };
|
||||
55C1A0103034101000786010 /* PhoneStatusCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneStatusCollector.swift; sourceTree = "<group>"; };
|
||||
55C1A0203034102000786020 /* PrivilegeDeniedOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivilegeDeniedOverlay.swift; sourceTree = "<group>"; };
|
||||
55C219C13024C00100784730 /* SearchLocationHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationHeaderView.swift; sourceTree = "<group>"; };
|
||||
55C4C0232FDB91C8009215C2 /* CheckPermissionGuideVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckPermissionGuideVC.swift; sourceTree = "<group>"; };
|
||||
55CA150B3058E55E000F1189 /* ScheduleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleViewModel.swift; sourceTree = "<group>"; };
|
||||
55CA150F3058EBE5000F1189 /* InteractionEmojiCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionEmojiCell.swift; sourceTree = "<group>"; };
|
||||
55CA15113058EC23000F1189 /* InteractionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionView.swift; sourceTree = "<group>"; };
|
||||
5E5F5DC8694A001BF0C47147 /* PairGuideStepsView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PairGuideStepsView.swift; path = AppRestrict/PairGuideStepsView.swift; sourceTree = "<group>"; };
|
||||
6BAE595D39202A2958737BD3 /* SelectActivityVC.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SelectActivityVC.swift; path = AppRestrict/SelectActivityVC.swift; sourceTree = "<group>"; };
|
||||
787F87DD1CEBED1A4E1E9FC7 /* AppRestrictView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppRestrictView.swift; path = AppRestrict/AppRestrictView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -949,7 +923,6 @@
|
|||
305A74CF2FCA8C7000227D26 /* UserAPI.swift */,
|
||||
55B230023032000100785003 /* PigeonAPI.swift */,
|
||||
30BAB8502FCD331C00C33B5C /* GroupAPI.swift */,
|
||||
30D891F42FE22E0600E958FD /* OrderAPI.swift */,
|
||||
30D74AB52FEA34FF0050EB2C /* ItineraryAPI.swift */,
|
||||
30B74B4A2FF390EF00F6744D /* DrivingAPI.swift */,
|
||||
);
|
||||
|
|
@ -1210,7 +1183,6 @@
|
|||
55B217D23022F0A000784728 /* Unlock */,
|
||||
305A752C2FCA8C7000227D26 /* URL */,
|
||||
30D87CD52FDF9F1900E958FD /* MQTT */,
|
||||
30C6666A2FFB7C3000E62B25 /* IAPManager */,
|
||||
2F518AB6587CC227CC515D8C /* AppRestrict */,
|
||||
);
|
||||
path = Manager;
|
||||
|
|
@ -1300,10 +1272,10 @@
|
|||
305A762A2FCA8C7000227D26 /* Home */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
55CA15113058EC23000F1189 /* InteractionView.swift */,
|
||||
55CA150F3058EBE5000F1189 /* InteractionEmojiCell.swift */,
|
||||
305A76292FCA8C7000227D26 /* HomeViewController.swift */,
|
||||
305A798B2FCAB99300227D26 /* HomeViewModel.swift */,
|
||||
305A76282FCA8C7000227D26 /* HomeView.swift */,
|
||||
305A76272FCA8C7000227D26 /* GroupMemberView.swift */,
|
||||
55B2179030217D6600784774 /* HomeView2.swift */,
|
||||
55B2179230218CE100784774 /* GroupMemberView2.swift */,
|
||||
55B222000000000100784830 /* AddEmojiPopView.swift */,
|
||||
|
|
@ -1319,8 +1291,6 @@
|
|||
55B240063040000100784906 /* AppUsageReport */,
|
||||
30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */,
|
||||
30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */,
|
||||
30D87CDC2FDFF07500E958FD /* InteractionView.swift */,
|
||||
55BF75342FFF91690055DA57 /* InteractionEmojiCell.swift */,
|
||||
55BF752C2FFE53F70055DA57 /* LocationPermissionPopView.swift */,
|
||||
30A87A692FEF59E60095E7C6 /* SearchLocation */,
|
||||
30A87A5C2FEE711C0095E7C6 /* Bubble */,
|
||||
|
|
@ -1398,7 +1368,6 @@
|
|||
305A762C2FCA8C7000227D26 /* Launch */,
|
||||
3062E8B32FCE6BA400CEF511 /* Scan */,
|
||||
30DC18592FD11E7A0041DCD1 /* Web */,
|
||||
30ACD54A2FF6332200174861 /* PopupWindow */,
|
||||
30EFF3AD2FD7FF1400EB35D4 /* TextInput */,
|
||||
30C6679D2FFB7FEF00E62B25 /* Share */,
|
||||
F49062649CA8E5F9FA2F22A5 /* AppRestrict */,
|
||||
|
|
@ -1415,7 +1384,6 @@
|
|||
55B230003032000100785001 /* UploadService.swift */,
|
||||
55B230043032000100785005 /* PigeonService.swift */,
|
||||
30BAB8522FCD337C00C33B5C /* GroupService.swift */,
|
||||
30D891F62FE22E6E00E958FD /* OrderService.swift */,
|
||||
30D74AB72FEA36A50050EB2C /* ItineraryService.swift */,
|
||||
30B74B4C2FF391FF00F6744D /* DrivingService.swift */,
|
||||
);
|
||||
|
|
@ -1515,7 +1483,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
55B26000304B000100786600 /* VersionUpgradePopVC.swift */,
|
||||
30CCDE4D2FE26CEA00F5214A /* PayResultPopVC.swift */,
|
||||
30EFF3BA2FD90D7600EB35D4 /* ConfirmPopVC.swift */,
|
||||
305A76642FCA8C7000227D26 /* DLAlertPopVC.swift */,
|
||||
305A76652FCA8C7000227D26 /* DLCustomPopVC.swift */,
|
||||
|
|
@ -1599,7 +1566,6 @@
|
|||
3062E8C12FCFB86800CEF511 /* CreateGroupViewModel.swift */,
|
||||
3062E8BD2FCEBD0E00CEF511 /* GroupIconListVC.swift */,
|
||||
3062E8BF2FCED7BB00CEF511 /* GroupIconListView.swift */,
|
||||
3062E8C32FCFC90F00CEF511 /* CreateGroupVipPopView.swift */,
|
||||
);
|
||||
path = CreateGroup;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1666,7 +1632,6 @@
|
|||
30A87A612FEE724D0095E7C6 /* CreateBubbleTiemView.swift */,
|
||||
30A87A632FEE75520095E7C6 /* CreateBubbleTipsView.swift */,
|
||||
30A87A652FEE843E0095E7C6 /* CreateBubbleDoneView.swift */,
|
||||
30A87A672FEE86560095E7C6 /* CreateBubblePopView.swift */,
|
||||
);
|
||||
path = Bubble;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1684,15 +1649,6 @@
|
|||
path = SearchLocation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
30ACD54A2FF6332200174861 /* PopupWindow */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30ACD67A2FF7B36A00174861 /* PopupQueueManager.swift */,
|
||||
30ACD54B2FF6336000174861 /* PopupWindow.swift */,
|
||||
);
|
||||
path = PopupWindow;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
30B2D0BA2FEB111100CA0001 /* PhotoPicker */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -1784,14 +1740,6 @@
|
|||
path = GroupInfo;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
30C6666A2FFB7C3000E62B25 /* IAPManager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30C6666B2FFB7C4000E62B25 /* IAPManager.swift */,
|
||||
);
|
||||
path = IAPManager;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
30C6679D2FFB7FEF00E62B25 /* Share */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -1827,9 +1775,7 @@
|
|||
30D74AAC2FEA13BD0050EB2C /* Schedule */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30D74AAF2FEA13ED0050EB2C /* ScheduleVC.swift */,
|
||||
30D74AB12FEA1D5D0050EB2C /* ScheduleViewModel.swift */,
|
||||
30D74AAD2FEA13E00050EB2C /* ScheduleView.swift */,
|
||||
55CA150B3058E55E000F1189 /* ScheduleViewModel.swift */,
|
||||
30D74AB32FEA25B90050EB2C /* ViewedModel.swift */,
|
||||
30D74AB92FEA37AD0050EB2C /* ScheduleModel.swift */,
|
||||
30D74ABB2FEA67CE0050EB2C /* CreateSchedule */,
|
||||
|
|
@ -1848,7 +1794,6 @@
|
|||
30D74D1E2FEBB09B0050EB2C /* CreateScheduleVM.swift */,
|
||||
30D74ABE2FEA67F30050EB2C /* CreateScheduleView.swift */,
|
||||
30D74AC02FEA6EEF0050EB2C /* CreateSchedulePopView.swift */,
|
||||
30DA36BC2FECC5AB008D5A2C /* CreateScheduleVipPopView.swift */,
|
||||
);
|
||||
path = CreateSchedule;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -2432,7 +2377,6 @@
|
|||
305A76922FCA8C7000227D26 /* LogUtils.swift in Sources */,
|
||||
305A76932FCA8C7000227D26 /* AddImageCell.swift in Sources */,
|
||||
30EFF3D82FDA8F1000EB35D4 /* EmergencyContactVC.swift in Sources */,
|
||||
30A87A682FEE86560095E7C6 /* CreateBubblePopView.swift in Sources */,
|
||||
55B219C13024C00100784722 /* BubbleHeroView.swift in Sources */,
|
||||
55B219C13024C00100784724 /* CreateBubbleSetupView.swift in Sources */,
|
||||
55B219C13024C00100784726 /* BubbleKnowledgeView.swift in Sources */,
|
||||
|
|
@ -2455,27 +2399,24 @@
|
|||
30EFF3DA2FDA935D00EB35D4 /* EmergencyContactFooterView.swift in Sources */,
|
||||
3062E8C22FCFB86800CEF511 /* CreateGroupViewModel.swift in Sources */,
|
||||
30A87A662FEE843E0095E7C6 /* CreateBubbleDoneView.swift in Sources */,
|
||||
55BF75352FFF91690055DA57 /* InteractionEmojiCell.swift in Sources */,
|
||||
305A769B2FCA8C7000227D26 /* PopupAnimators.swift in Sources */,
|
||||
30A87A4D2FEE1DB40095E7C6 /* ItineraryDetailView.swift in Sources */,
|
||||
305A769C2FCA8C7000227D26 /* PopupViewController.swift in Sources */,
|
||||
305A769D2FCA8C7000227D26 /* PopupViewController+Extension.swift in Sources */,
|
||||
30EFF3C42FDA431D00EB35D4 /* ChangePhoneVC.swift in Sources */,
|
||||
30C6666C2FFB7C4000E62B25 /* IAPManager.swift in Sources */,
|
||||
305A769E2FCA8C7000227D26 /* TextArrowCell.swift in Sources */,
|
||||
305A769F2FCA8C7000227D26 /* TextContentArrowCell.swift in Sources */,
|
||||
30D87D042FE1336300E958FD /* NavigationVC.swift in Sources */,
|
||||
30D87D052FE1336300E958FD /* NavigationView.swift in Sources */,
|
||||
30B819F42FF3CE4900FAB693 /* ItineraryTraceVC.swift in Sources */,
|
||||
305A76A02FCA8C7000227D26 /* TextTableViewCell.swift in Sources */,
|
||||
30ACD67B2FF7B36A00174861 /* PopupQueueManager.swift in Sources */,
|
||||
305A76A12FCA8C7000227D26 /* UIButton+RTL.m in Sources */,
|
||||
30EFF3A62FD7C5AF00EB35D4 /* GroupSettingVC.swift in Sources */,
|
||||
55CA15103058EBE5000F1189 /* InteractionEmojiCell.swift in Sources */,
|
||||
305A76A22FCA8C7000227D26 /* Array+Extension.swift in Sources */,
|
||||
305A76A32FCA8C7000227D26 /* ControlEvents+Block.swift in Sources */,
|
||||
3062E8B72FCE6BFE00CEF511 /* ScanView.swift in Sources */,
|
||||
305A76A42FCA8C7000227D26 /* Date+Extension.swift in Sources */,
|
||||
30D74AAE2FEA13E00050EB2C /* ScheduleView.swift in Sources */,
|
||||
305A76A52FCA8C7000227D26 /* Dictionay+Extension.swift in Sources */,
|
||||
30A87A522FEE4E5D0095E7C6 /* ScheduleHistoryVC.swift in Sources */,
|
||||
305A76A62FCA8C7000227D26 /* Int+Extension.swift in Sources */,
|
||||
|
|
@ -2566,9 +2507,11 @@
|
|||
55B234013036000100785051 /* RelationSelectPopVC.swift in Sources */,
|
||||
55B234033036000100785053 /* RelationStore.swift in Sources */,
|
||||
55B220013031000100784801 /* FeedbackVC.swift in Sources */,
|
||||
55CA15123058EC23000F1189 /* InteractionView.swift in Sources */,
|
||||
55B220033031000100784803 /* FeedbackView.swift in Sources */,
|
||||
55B220053031000100784805 /* FeedbackSuccessView.swift in Sources */,
|
||||
55B221210000000100784821 /* MoodStatusVC.swift in Sources */,
|
||||
55CA150E3058E55E000F1189 /* ScheduleViewModel.swift in Sources */,
|
||||
55B221230000000100784823 /* MoodStatusView.swift in Sources */,
|
||||
55B222010000000100784831 /* AddEmojiPopView.swift in Sources */,
|
||||
55B222030000000100784833 /* AddMessagePopView.swift in Sources */,
|
||||
|
|
@ -2588,7 +2531,6 @@
|
|||
305A76C72FCA8C7000227D26 /* UserConfigModel.swift in Sources */,
|
||||
30B74B472FF3608600F6744D /* ScheduleRecordModel.swift in Sources */,
|
||||
305A76C82FCA8C7000227D26 /* UserConfigResponse.swift in Sources */,
|
||||
30ACD54C2FF6336000174861 /* PopupWindow.swift in Sources */,
|
||||
305A76C92FCA8C7000227D26 /* ApiManager.swift in Sources */,
|
||||
305A76CA2FCA8C7000227D26 /* AppSettings.swift in Sources */,
|
||||
305A76CB2FCA8C7000227D26 /* Authorize.swift in Sources */,
|
||||
|
|
@ -2596,7 +2538,6 @@
|
|||
30C6679E2FFB7FEF00E62B25 /* SharePopView.swift in Sources */,
|
||||
305A76CC2FCA8C7000227D26 /* FileTools.swift in Sources */,
|
||||
305A76CD2FCA8C7000227D26 /* Permission.swift in Sources */,
|
||||
30D74AB02FEA13ED0050EB2C /* ScheduleVC.swift in Sources */,
|
||||
55B218B13024B00100784722 /* FeatureIntroVC.swift in Sources */,
|
||||
55B218B13024B00100784724 /* FeatureIntroView.swift in Sources */,
|
||||
305A76CE2FCA8C7000227D26 /* RouterManager.swift in Sources */,
|
||||
|
|
@ -2641,7 +2582,6 @@
|
|||
55B250043038000100786504 /* MailboxView.swift in Sources */,
|
||||
55B250063038000100786506 /* MailboxViewController.swift in Sources */,
|
||||
55B250083038000100786508 /* MailboxDetailViewController.swift in Sources */,
|
||||
30D74AB22FEA1D5D0050EB2C /* ScheduleViewModel.swift in Sources */,
|
||||
30EFF3B52FD8F1D000EB35D4 /* ReviewMemberListVC.swift in Sources */,
|
||||
30BAB84D2FCD2FDE00C33B5C /* InviteJoinView.swift in Sources */,
|
||||
30A87A592FEE5A4C0095E7C6 /* ScheduleViewedView.swift in Sources */,
|
||||
|
|
@ -2649,10 +2589,7 @@
|
|||
30CCDE5C2FE3A1A800F5214A /* SOSPracticeView.swift in Sources */,
|
||||
30EFF3E52FDAA93400EB35D4 /* PrivacyPolicyVC.swift in Sources */,
|
||||
305A76E22FCA8C7000227D26 /* GroupViewModel.swift in Sources */,
|
||||
30CCDE4E2FE26CEA00F5214A /* PayResultPopVC.swift in Sources */,
|
||||
305A76E32FCA8C7000227D26 /* GroupMemberView.swift in Sources */,
|
||||
30BAB84F2FCD2FED00C33B5C /* InviteJoinVC.swift in Sources */,
|
||||
305A76E42FCA8C7000227D26 /* HomeView.swift in Sources */,
|
||||
305A76E52FCA8C7000227D26 /* HomeViewController.swift in Sources */,
|
||||
305A76E62FCA8C7000227D26 /* LaunchViewController.swift in Sources */,
|
||||
3062E8B52FCE6BBA00CEF511 /* ScanVC.swift in Sources */,
|
||||
|
|
@ -2722,16 +2659,13 @@
|
|||
305A77032FCA8C7000227D26 /* ProgressHUD.swift in Sources */,
|
||||
305A77042FCA8C7000227D26 /* MXParallaxHeader.m in Sources */,
|
||||
305A77052FCA8C7000227D26 /* MXScrollView.m in Sources */,
|
||||
30D87CDD2FDFF07500E958FD /* InteractionView.swift in Sources */,
|
||||
30BAB8652FCD718A00C33B5C /* JoinGroupView.swift in Sources */,
|
||||
305A77062FCA8C7000227D26 /* MXScrollViewController.m in Sources */,
|
||||
30B74B432FF2438800F6744D /* GroupMemberListView.swift in Sources */,
|
||||
305A77072FCA8C7000227D26 /* Helper.swift in Sources */,
|
||||
30BF30102FED0C8E00D9CB52 /* ScheduleDetailVM.swift in Sources */,
|
||||
30DA36BD2FECC5AB008D5A2C /* CreateScheduleVipPopView.swift in Sources */,
|
||||
305A77082FCA8C7000227D26 /* PageCollectionViewFlowLayout.swift in Sources */,
|
||||
55BF752D2FFE53F70055DA57 /* LocationPermissionPopView.swift in Sources */,
|
||||
3062E8C42FCFC90F00CEF511 /* CreateGroupVipPopView.swift in Sources */,
|
||||
30D74ABA2FEA37AD0050EB2C /* ScheduleModel.swift in Sources */,
|
||||
30CCDE512FE2785D00F5214A /* SignInVC.swift in Sources */,
|
||||
305A77092FCA8C7000227D26 /* PageContentView.swift in Sources */,
|
||||
|
|
@ -2739,7 +2673,6 @@
|
|||
305A770B2FCA8C7000227D26 /* PageTitleView.swift in Sources */,
|
||||
305A770C2FCA8C7000227D26 /* PageView.swift in Sources */,
|
||||
305A770D2FCA8C7000227D26 /* PageViewManager.swift in Sources */,
|
||||
30D891F52FE22E0600E958FD /* OrderAPI.swift in Sources */,
|
||||
307073EA2FD2715A004C37CC /* GroupChatViewModel.swift in Sources */,
|
||||
305A770E2FCA8C7000227D26 /* DLAlertPopVC.swift in Sources */,
|
||||
305A770F2FCA8C7000227D26 /* DLCustomPopVC.swift in Sources */,
|
||||
|
|
@ -2751,7 +2684,6 @@
|
|||
30EFF3B72FD8F86200EB35D4 /* ReviewMemberListVM.swift in Sources */,
|
||||
305A77112FCA8C7000227D26 /* DLViewTransition.m in Sources */,
|
||||
3062E8BA2FCEAC6500CEF511 /* CreateGroupView.swift in Sources */,
|
||||
30D891F72FE22E6E00E958FD /* OrderService.swift in Sources */,
|
||||
305A77192FCA8C7000227D26 /* CollectionHFlowLayout.swift in Sources */,
|
||||
30D74AC12FEA6EEF0050EB2C /* CreateSchedulePopView.swift in Sources */,
|
||||
305A771A2FCA8C7000227D26 /* JJPageControl.swift in Sources */,
|
||||
|
|
@ -2897,7 +2829,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = LRDLWZ2Y83;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = "$(inherited)";
|
||||
|
|
@ -2952,7 +2884,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = LRDLWZ2Y83;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = "$(inherited)";
|
||||
|
|
@ -3185,7 +3117,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = LRDLWZ2Y83;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = "$(inherited)";
|
||||
|
|
|
|||
|
|
@ -151,12 +151,6 @@ enum GatewayStatusCode: Int {
|
|||
// 审核中
|
||||
case review = 201
|
||||
|
||||
/** ============== 业务 ============== */
|
||||
// 需要开通vip
|
||||
case needVip = 20000
|
||||
// 您创建的圈子个数已达上限,请升级会员等级
|
||||
case groupLimit = 20009
|
||||
|
||||
/** ============== 未知错误 ============== */
|
||||
case unknownError = -9999
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
//
|
||||
// OrderAPI.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/17.
|
||||
//
|
||||
|
||||
import Moya
|
||||
import SwiftyUserDefaults
|
||||
internal import Alamofire
|
||||
|
||||
/// 订单API
|
||||
enum OrderAPI {
|
||||
/// 充值内容
|
||||
/// - Parameters:
|
||||
/// - type: 类型 member
|
||||
case rechargeInfo(type: String)
|
||||
|
||||
/// 支付参数
|
||||
/// - Parameters:
|
||||
/// - goodsId: 商品ID
|
||||
/// - payType: alipay、weixin
|
||||
/// - source: center
|
||||
/// - extra: 额外参数
|
||||
case orderPayParams(goodsId: String, payType: String, source: String, extra: [String:Any]=[:])
|
||||
|
||||
/// Apple 内购通知
|
||||
case appleNotify(receipt: String, productId: String, transactionId: String)
|
||||
}
|
||||
|
||||
extension OrderAPI: MultiTargetProtocol {
|
||||
|
||||
var path: String {
|
||||
switch self {
|
||||
case .rechargeInfo:
|
||||
return "api/order/goods"
|
||||
case .orderPayParams:
|
||||
return "api/order"
|
||||
case .appleNotify:
|
||||
return "api/order/notify/apple"
|
||||
}
|
||||
}
|
||||
|
||||
var method: Moya.Method {
|
||||
switch self {
|
||||
case .rechargeInfo:
|
||||
return .get
|
||||
default:
|
||||
return .post
|
||||
}
|
||||
}
|
||||
|
||||
var task: Moya.Task {
|
||||
switch self {
|
||||
case let .rechargeInfo(type):
|
||||
var params = Parameters()
|
||||
params["type"] = type
|
||||
return .requestParameters(parameters: params, encoding: URLEncoding())
|
||||
|
||||
case let .orderPayParams(goodsId, payType, source, extra):
|
||||
var params = Parameters()
|
||||
params["goods_id"] = goodsId
|
||||
params["pay_type"] = payType
|
||||
params["source"] = source
|
||||
params["pay_source"] = "app"
|
||||
params["extra"] = extra
|
||||
return .requestParameters(parameters: params, encoding: JSONEncoding())
|
||||
|
||||
case let .appleNotify(receipt, productId, transactionId):
|
||||
var params = Parameters()
|
||||
params["receipt_data"] = receipt
|
||||
params["product_id"] = productId
|
||||
params["transaction_id"] = transactionId
|
||||
return .requestParameters(parameters: params, encoding: JSONEncoding())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -38,8 +38,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
SwiftDate.defaultRegion = Region(zone: TimeZone.current, locale: Locale.current)
|
||||
|
||||
IAPManager.shared.startObserving()
|
||||
|
||||
IQKeyboardManager.shared.isEnabled = true
|
||||
IQKeyboardManager.shared.enableAutoToolbar = false
|
||||
IQKeyboardManager.shared.resignOnTouchOutside = true
|
||||
|
|
@ -91,25 +89,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 处理支付宝支付结果(不限制 host,适应新版 SDK)
|
||||
if url.scheme?.hasPrefix("Alipay") == true || url.host == "safepay" {
|
||||
AlipaySDK.defaultService().processOrder(withPaymentResult: url) { resultDic in
|
||||
print("📱 Alipay callback: \(resultDic ?? [:])")
|
||||
guard let result = resultDic,
|
||||
let resultStatus = result["resultStatus"] as? String,
|
||||
resultStatus != "6001" else { return }
|
||||
if resultStatus == "9000" {
|
||||
NotificationCenter.default.post(name: .RequestOrderPayStatusNotification, object: nil)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// else if url.host == "pay" {
|
||||
return WXApi.handleOpen(url, delegate: self)
|
||||
// }
|
||||
//
|
||||
// return true
|
||||
return WXApi.handleOpen(url, delegate: self)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication,
|
||||
|
|
@ -197,22 +177,5 @@ extension AppDelegate: WXApiDelegate {
|
|||
handleWechatLoginCode(code)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.isKind(of: SendAuthResp.self) {
|
||||
NotificationCenter.default.post(name: .resumePopupQueue, object: nil)
|
||||
}
|
||||
|
||||
if resp.isKind(of: PayResp.self),
|
||||
let response: PayResp = resp as? PayResp {
|
||||
print("===================== WxPay =====================")
|
||||
print(response.errStr)
|
||||
if response.errCode == 0 { // 支付成功
|
||||
DispatchQueue.global().async {
|
||||
DispatchQueue.main.async {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group_2040@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group_2040@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 284 KiB |
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group_2041@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group_2041@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 294 KiB |
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group_2042@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group_2042@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 282 KiB |
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group_2043@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group_2043@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 270 KiB |
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group_2044@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group_2044@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 272 KiB |
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "today_track@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "today_track@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "按钮@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "按钮@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "crown@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "crown@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 681 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "narrow_button_bg@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "narrow_button_bg@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "wide_button_bg@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "wide_button_bg@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
|
@ -63,13 +63,6 @@ class AppContextManager: NSObject {
|
|||
account?.sex ?? -1
|
||||
}
|
||||
|
||||
/// 会员 1:非会员 2:普通会员 3:终身会员
|
||||
var vip: Int {
|
||||
guard AppContextManager.shared.systemConfig?.isIntercept == false else {
|
||||
return 2
|
||||
}
|
||||
return account?.vip ?? 1
|
||||
}
|
||||
/// 状态:0、无状态 1、SOS 2、气泡等
|
||||
var status: Int {
|
||||
account?.status ?? 0
|
||||
|
|
|
|||
|
|
@ -32,12 +32,6 @@ struct UserConfigModel: Mappable {
|
|||
/// 用户 ID
|
||||
var uid: String?
|
||||
|
||||
/// 会员 1:非会员 2:普通会员 3:终身会员
|
||||
var vip: Int = 1
|
||||
/// 会员类型
|
||||
var vip_name: String = ""
|
||||
/// 会员过期时间
|
||||
var vip_expire_time: String = ""
|
||||
/// 状态:0、无状态 1、SOS 2、气泡等
|
||||
var status: Int = 0
|
||||
/// 心情:0、无状态 1、开心 2、愤怒
|
||||
|
|
@ -59,9 +53,6 @@ struct UserConfigModel: Mappable {
|
|||
sex <- map["sex"]
|
||||
head_pic <- (map["head_pic"], kFlexString)
|
||||
config <- map["config"]
|
||||
vip <- map["vip"]
|
||||
vip_name <- map["vip_name"]
|
||||
vip_expire_time <- map["vip_expire_time"]
|
||||
status <- map["status"]
|
||||
mood <- map["mood"]
|
||||
}
|
||||
|
|
@ -70,9 +61,9 @@ struct UserConfigModel: Mappable {
|
|||
struct SystemConfigModel: Mappable {
|
||||
|
||||
/// 弹窗配置
|
||||
var popupConfig: PopupWindowConfigModel?
|
||||
// var popupConfig: PopupWindowConfigModel?
|
||||
/// 弹窗时间配置
|
||||
var timeConfig: PopupWindowTimeModel?
|
||||
// var timeConfig: PopupWindowTimeModel?
|
||||
/// 微信分享配置
|
||||
var shareConfig: WxShareConfigModel?
|
||||
/// 飞鸽传书发送次数
|
||||
|
|
@ -89,17 +80,6 @@ struct SystemConfigModel: Mappable {
|
|||
/// 圈子里的banner
|
||||
var groupBannerList: [String] = []
|
||||
|
||||
/// 引导页显示开关
|
||||
var guideDisplay: Bool = true
|
||||
|
||||
/// 全局控制显示隐藏 弹窗和充值
|
||||
var globalDisplay: Bool = true
|
||||
|
||||
/// 是否拦截
|
||||
var isIntercept: Bool {
|
||||
return globalDisplay == false || !WXApi.isWXAppInstalled() // 未安装微信
|
||||
}
|
||||
|
||||
init?(map: Map) {
|
||||
|
||||
}
|
||||
|
|
@ -107,63 +87,14 @@ struct SystemConfigModel: Mappable {
|
|||
mutating func mapping(map: Map) {
|
||||
chatWarning <- map["client.chatwarning", nested: false]
|
||||
groupBannerList <- map["client.team.ad", nested: false]
|
||||
popupConfig <- map["client.popup.display", nested: false]
|
||||
timeConfig <- map["client.time.setting", nested: false]
|
||||
// timeConfig <- map["client.time.setting", nested: false]
|
||||
pigeonConfig <- map["client.time.setting.pigeon", nested: false]
|
||||
shareConfig <- map["client.weixin.share", nested: false]
|
||||
guideDisplay <- map["client.guide_display", nested: false]
|
||||
globalDisplay <- map["client.global_display", nested: false]
|
||||
versionUpgradeModel <- map["client.version.upgrade", nested: false]
|
||||
bannerList <- map["client.banner.explore", nested: false]
|
||||
}
|
||||
}
|
||||
|
||||
struct PopupWindowConfigModel: Mappable {
|
||||
/// 是否弹vip弹窗
|
||||
var pay_pop: Bool = true
|
||||
|
||||
/// 搜索位置弹窗
|
||||
var search_phone: Bool = true
|
||||
|
||||
|
||||
init?(map: Map) {
|
||||
|
||||
}
|
||||
|
||||
mutating func mapping(map: Map) {
|
||||
pay_pop <- map["pay_pop"]
|
||||
search_phone <- map["search_phone"]
|
||||
}
|
||||
}
|
||||
|
||||
struct PopupWindowTimeModel: Mappable {
|
||||
/// 会员充值push倒计时
|
||||
var buy_vip_tip: Int = 0
|
||||
/// 搜索位置 倒计时(秒)
|
||||
var search_phone: Int = 0
|
||||
/// 注册提示
|
||||
var signup_tip: Int = 0
|
||||
/// vip提示倒计时(秒)
|
||||
var pay_pop_time: Int = 0
|
||||
/// vip折扣
|
||||
var min_discount: Double = 0
|
||||
/// 默认支付方式 1 支付宝 2 微信
|
||||
var pay_pop_type: Int = 0
|
||||
|
||||
init?(map: Map) {
|
||||
|
||||
}
|
||||
|
||||
mutating func mapping(map: Map) {
|
||||
buy_vip_tip <- map["buy_vip_tip"]
|
||||
search_phone <- map["search_phone"]
|
||||
signup_tip <- map["signup_tip"]
|
||||
pay_pop_time <- map["pay_pop_time"]
|
||||
min_discount <- map["min_discount"]
|
||||
pay_pop_type <- map["pay_pop_type"]
|
||||
}
|
||||
}
|
||||
|
||||
struct PigeonTimeSettingModel: Mappable {
|
||||
/// 时间窗口内最多发送次数
|
||||
var nums: Int = 0
|
||||
|
|
@ -237,7 +168,7 @@ struct VersionUpgradeModel: Mappable {
|
|||
struct BannerConfigModel: Mappable {
|
||||
/// 图片地址
|
||||
var img: String = ""
|
||||
/// url:member跳充值页 url:网页地址
|
||||
/// url:member跳cz页 url:网页地址
|
||||
var url: String = ""
|
||||
/// app:原生应用内跳转 web:跳转网页
|
||||
var type: String = ""
|
||||
|
|
|
|||
|
|
@ -114,8 +114,6 @@ extension ApiManager {
|
|||
handleTokenExpired(msg: message)
|
||||
case GatewayStatusCode.failure.rawValue, GatewayStatusCode.noAuthority.rawValue: // 退出当前界面
|
||||
handlePopView(message, handle)
|
||||
case GatewayStatusCode.needVip.rawValue:
|
||||
handleNeedVip(message)
|
||||
default:
|
||||
// 接口没返回code的时候取success字段
|
||||
if success != nil {
|
||||
|
|
@ -136,7 +134,6 @@ extension ApiManager {
|
|||
return .failure(handleError(with: code,
|
||||
domain: "Data Error",
|
||||
message: message,
|
||||
isShowTip: code != GatewayStatusCode.needVip.rawValue,
|
||||
data: response))
|
||||
case let .failure(error):
|
||||
let message = error.gatewayMessage ?? "请检查网络连接"
|
||||
|
|
@ -197,14 +194,6 @@ extension ApiManager {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - 需开通VIP
|
||||
private func handleNeedVip(_ msg: String) {
|
||||
MainAsync {
|
||||
UIViewController.showConfirmPop(title: "温馨提示", message: msg, confirmText: "去开通", confirmBlock: {
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
}, cancelText: "取消")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ApiManager {
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
//
|
||||
// IAPManager.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/7/3.
|
||||
//
|
||||
|
||||
import StoreKit
|
||||
import RxSwift
|
||||
|
||||
class IAPManager: NSObject {
|
||||
static let shared = IAPManager()
|
||||
private let disposeBag = DisposeBag()
|
||||
|
||||
private var currentGoodsId: String = ""
|
||||
private var purchaseCompletion: ((Bool, String?) -> Void)?
|
||||
|
||||
func startObserving() {
|
||||
SKPaymentQueue.default().add(self)
|
||||
}
|
||||
|
||||
func purchase(goodsId: String, completion: @escaping (Bool, String?) -> Void) {
|
||||
currentGoodsId = goodsId
|
||||
purchaseCompletion = completion
|
||||
let request = SKProductsRequest(productIdentifiers: [goodsId])
|
||||
request.delegate = self
|
||||
request.start()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SKProductsRequestDelegate
|
||||
extension IAPManager: SKProductsRequestDelegate {
|
||||
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
|
||||
guard let product = response.products.first else {
|
||||
purchaseCompletion?(false, "商品不可用")
|
||||
return
|
||||
}
|
||||
SKPaymentQueue.default().add(SKPayment(product: product))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SKPaymentTransactionObserver
|
||||
extension IAPManager: SKPaymentTransactionObserver {
|
||||
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
||||
for tx in transactions {
|
||||
switch tx.transactionState {
|
||||
case .purchased:
|
||||
verifyReceipt(transaction: tx)
|
||||
case .failed:
|
||||
queue.finishTransaction(tx)
|
||||
let msg = tx.error?.localizedDescription ?? "支付失败"
|
||||
purchaseCompletion?(false, msg)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func verifyReceipt(transaction: SKPaymentTransaction) {
|
||||
guard let receiptURL = Bundle.main.appStoreReceiptURL,
|
||||
let receiptData = try? Data(contentsOf: receiptURL) else {
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
purchaseCompletion?(false, "获取凭证失败")
|
||||
return
|
||||
}
|
||||
let receipt = receiptData.base64EncodedString()
|
||||
let txId = transaction.transactionIdentifier ?? ""
|
||||
|
||||
OrderService.appleNotify(
|
||||
receipt: receipt,
|
||||
productId: currentGoodsId,
|
||||
transactionId: txId
|
||||
).subscribe(onNext: { [weak self] _ in
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
self?.purchaseCompletion?(true, nil)
|
||||
}, onError: { [weak self] error in
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
self?.purchaseCompletion?(false, error.localizedDescription)
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
}
|
||||
|
|
@ -92,7 +92,6 @@ struct GroupUsersResponse: BaseModelProtocol {
|
|||
var message: String?
|
||||
var model: GroupInfoModel?
|
||||
var list: [GroupMemberModel] = []
|
||||
var vip = GroupMemberVip()
|
||||
var reviewCount: Int = 0
|
||||
|
||||
init?(map: Map) {}
|
||||
|
|
@ -102,7 +101,6 @@ struct GroupUsersResponse: BaseModelProtocol {
|
|||
message <- map["message"]
|
||||
model <- map["data.group"]
|
||||
list <- map["data.mates"]
|
||||
vip <- map["data.vip"]
|
||||
reviewCount <- (map["data.reviewCount"], kStrTransformInt)
|
||||
if reviewCount == 0 {
|
||||
reviewCount <- (map["data.review_count"], kStrTransformInt)
|
||||
|
|
@ -231,7 +229,7 @@ struct GroupInfoModel: Mappable, Equatable {
|
|||
var people_no: Int = 0
|
||||
/// 描述
|
||||
var description: String = ""
|
||||
/// 会员等级
|
||||
/// 等级
|
||||
var level: String = ""
|
||||
/// 邀请码
|
||||
var share_code: String = ""
|
||||
|
|
@ -288,27 +286,6 @@ struct GroupPrivilege: Mappable, Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
struct GroupMemberVip: Mappable, Equatable {
|
||||
var app_usage_record: Bool?
|
||||
var phone_report: Bool?
|
||||
var trip_analysis_others: Bool?
|
||||
var app_lock: Bool?
|
||||
var pigeon_mail: Bool?
|
||||
var view_phone: Bool?
|
||||
|
||||
init() {}
|
||||
init?(map: Map) {}
|
||||
|
||||
mutating func mapping(map: Map) {
|
||||
app_usage_record <- (map["app_usage_record"], kOptionalFlexibleBool)
|
||||
phone_report <- (map["phone_report"], kOptionalFlexibleBool)
|
||||
trip_analysis_others <- (map["trip_analysis_others"], kOptionalFlexibleBool)
|
||||
app_lock <- (map["app_lock"], kOptionalFlexibleBool)
|
||||
pigeon_mail <- (map["pigeon_mail"], kOptionalFlexibleBool)
|
||||
view_phone <- (map["view_phone"], kOptionalFlexibleBool)
|
||||
}
|
||||
}
|
||||
|
||||
private let kFlexibleBool = TransformOf<Bool, Any>(fromJSON: { value in
|
||||
switch value {
|
||||
case let value as Bool:
|
||||
|
|
@ -327,31 +304,6 @@ private let kFlexibleBool = TransformOf<Bool, Any>(fromJSON: { value in
|
|||
value
|
||||
})
|
||||
|
||||
private let kOptionalFlexibleBool = TransformOf<Bool, Any>(fromJSON: { value in
|
||||
switch value {
|
||||
case let value as Bool:
|
||||
return value
|
||||
case let value as NSNumber:
|
||||
return value.boolValue
|
||||
case let value as Int:
|
||||
return value != 0
|
||||
case let value as String:
|
||||
let text = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
switch text {
|
||||
case "1", "true", "yes":
|
||||
return true
|
||||
case "0", "false", "no":
|
||||
return false
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}, toJSON: { value in
|
||||
value
|
||||
})
|
||||
|
||||
extension UIImageView {
|
||||
@MainActor
|
||||
func setGroupIcon(_ model: GroupInfoModel) {
|
||||
|
|
@ -470,11 +422,6 @@ struct GroupMemberModel: Mappable, Equatable {
|
|||
remark.isEmpty ? nick_name : remark
|
||||
}
|
||||
|
||||
/// 会员等级 1普通 2白银 3永久 4钻石
|
||||
var level: Int = 1
|
||||
var shouldShowVipAvatarFrame: Bool {
|
||||
AppContextManager.shared.systemConfig?.isIntercept != true && level != 1
|
||||
}
|
||||
/// 状态:0、无状态 1、SOS 2、气泡等
|
||||
var status: Int = 0
|
||||
/// 入圈天数(若服务端下发)
|
||||
|
|
@ -530,7 +477,6 @@ struct GroupMemberModel: Mappable, Equatable {
|
|||
if avater.isEmpty {
|
||||
avater <- (map["avatar"], kFlexString)
|
||||
}
|
||||
level <- (map["level"], kStrTransformInt)
|
||||
nick_name <- map["nick_name"]
|
||||
remark <- map["remark"]
|
||||
battery <- map["battery"]
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
{"v":"5.12.1","fr":25,"ip":0,"op":110,"w":375,"h":375,"nm":"合成 1","ddd":0,"assets":[{"id":"image_0","w":480,"h":480,"u":"images/","p":"dialog_buy_vip3_img_0.png","e":0},{"id":"image_1","w":192,"h":160,"u":"images/","p":"dialog_buy_vip3_img_1.png","e":0}],"layers":[{"ddd":0,"ind":1,"ty":2,"nm":"image 112.png","cl":"png","refId":"image_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[306,117.5,0],"to":[0,0,0],"ti":[117,4,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18.462,"s":[226,248,0],"to":[-117,-4,0],"ti":[-39.5,36,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":36.923,"s":[125,136,0],"to":[6.063,-5.526,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":55.385,"s":[232.5,144,0],"to":[0,0,0],"ti":[140.5,-22,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.846,"s":[167.5,284,0],"to":[-140.5,22,0],"ti":[-39.436,13.632,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.308,"s":[73.5,227,0],"to":[40.5,-14,0],"ti":[-22,-19,0]},{"t":110.76953125,"s":[212.5,285.5,0]}],"ix":2,"l":2},"a":{"a":0,"k":[243.995,243.45,0],"ix":1,"l":2},"s":{"a":0,"k":[11.792,11.792,100],"ix":6,"l":2}},"ao":1,"ip":0,"op":126,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":2,"nm":"图层 30 1.png","cl":"png","refId":"image_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[195,210.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18.445,"s":[195,181.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22,"s":[195,197.089,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":36.892,"s":[195,210.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":55.337,"s":[195,181.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.783,"s":[195,210.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.229,"s":[195,181.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":110.673828125,"s":[195,210.5,0]}],"ix":2,"l":2},"a":{"a":0,"k":[96,80,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":126,"st":0,"bm":0}],"markers":[],"props":{}}
|
||||
|
|
@ -35,8 +35,7 @@ final class FeatureIntroVC: BaseViewController {
|
|||
super.viewDidAppear(animated)
|
||||
|
||||
rootView.reloadFeatureItems()
|
||||
guard AppContextManager.shared.systemConfig?.isIntercept == false,
|
||||
let config = AppContextManager.shared.systemConfig else { return }
|
||||
guard let config = AppContextManager.shared.systemConfig else { return }
|
||||
bannerList = config.bannerList
|
||||
rootView.cycleScrollView.isHidden = config.bannerList.filter { $0.enable == true }.count == 0
|
||||
rootView.cycleScrollView.imageURLStringsGroup = config.bannerList.filter { $0.enable == true }.map { $0.img }
|
||||
|
|
@ -58,7 +57,6 @@ final class FeatureIntroVC: BaseViewController {
|
|||
case .pigeonMessage:
|
||||
AppRouter.push(Route.pigeonMessage)
|
||||
case .searchLocation:
|
||||
guard AppContextManager.shared.systemConfig?.isIntercept != true else { return }
|
||||
AppRouter.push(Route.searchLocation)
|
||||
case .sos:
|
||||
AppRouter.push(Route.sos)
|
||||
|
|
|
|||
|
|
@ -154,8 +154,7 @@ final class FeatureIntroView: UIView {
|
|||
}
|
||||
|
||||
func reloadFeatureItems() {
|
||||
let hideSearch = AppContextManager.shared.systemConfig?.isIntercept == true
|
||||
displayedItems = Self.allItems.filter { !hideSearch || $0.action != .searchLocation }
|
||||
displayedItems = Self.allItems
|
||||
rebuildGrid()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -128,15 +128,7 @@ class CreateGroupViewModel {
|
|||
"labels": selectedTagList]).subscribe(onNext: { [weak self] _ in
|
||||
self?.createSucceeded.accept(())
|
||||
}, onError: { (error) in
|
||||
guard let code = error.underlyingError?.code else { return }
|
||||
|
||||
guard let systemConfig = AppContextManager.shared.systemConfig, systemConfig.isIntercept == false else { return }
|
||||
if code == 20009 { // "您创建的圈子个数已达上限,请升级会员等级"
|
||||
CreateGroupVipPopView.show()
|
||||
}
|
||||
else {
|
||||
DLToast.show(error)
|
||||
}
|
||||
DLToast.show(error)
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,120 +0,0 @@
|
|||
//
|
||||
// CreateGroupVipPopView.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/3.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
|
||||
class CreateGroupVipPopView: UIView {
|
||||
|
||||
private static let shared = CreateGroupVipPopView(frame: CGRect(origin: .zero, size: kScreenSize))
|
||||
|
||||
var disposeBag = DisposeBag()
|
||||
|
||||
static func show() {
|
||||
guard let superView = kKeyWindow else {
|
||||
return
|
||||
}
|
||||
|
||||
if CreateGroupVipPopView.shared.superview != nil {
|
||||
CreateGroupVipPopView.shared.removeFromSuperview()
|
||||
CreateGroupVipPopView.shared.bgView.frame = .zero
|
||||
}
|
||||
CreateGroupVipPopView.shared.bgView.alpha = 1
|
||||
CreateGroupVipPopView.shared.bgView.frame = CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight)
|
||||
superView.addSubview(CreateGroupVipPopView.shared)
|
||||
superView.bringSubviewToFront(CreateGroupVipPopView.shared)
|
||||
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
CreateGroupVipPopView.shared.bgView.alpha = 1
|
||||
}
|
||||
}
|
||||
|
||||
/// 关闭
|
||||
static func dismiss() {
|
||||
guard CreateGroupVipPopView.shared.superview != nil else { return }
|
||||
|
||||
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseIn]) {
|
||||
CreateGroupVipPopView.shared.bgView.alpha = 0
|
||||
} completion: { _ in
|
||||
CreateGroupVipPopView.shared.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
private func setupRx() {
|
||||
upgradedBtn.rx.tap.subscribe(onNext: { _ in
|
||||
CreateGroupVipPopView.dismiss()
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
closeBtn.rx.tap.subscribe(onNext: { _ in
|
||||
CreateGroupVipPopView.dismiss()
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private lazy var bgView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .black.withAlphaComponent(0.5)
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var vipImgView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Group/vip_pop")
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var upgradedBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.backgroundColor = .clear
|
||||
btn.setBackgroundImage(UIImage(named: "Group/upgrade_bg"), for: .normal)
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var closeBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.backgroundColor = .clear
|
||||
btn.setBackgroundImage(UIImage(named: "Group/close"), for: .normal)
|
||||
btn.extendEdgeInsets = UIEdgeInsets(top: 10, left: 100, bottom: 100, right: 100)
|
||||
return btn
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .clear
|
||||
addSubview(bgView)
|
||||
bgView.addSubview(vipImgView)
|
||||
bgView.addSubview(upgradedBtn)
|
||||
bgView.addSubview(closeBtn)
|
||||
|
||||
vipImgView.layoutChain
|
||||
.centerY()
|
||||
.edgesHorzontal(25)
|
||||
.heightToWidth(814/648)
|
||||
|
||||
upgradedBtn.layoutChain
|
||||
.topToBottomOfView(vipImgView, offset: -20)
|
||||
.centerX()
|
||||
.width(240)
|
||||
.height(60)
|
||||
|
||||
closeBtn.layoutChain
|
||||
.topToBottomOfView(upgradedBtn, offset: 15)
|
||||
.centerX()
|
||||
.width(22)
|
||||
.height(22)
|
||||
|
||||
setupRx()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -96,7 +96,6 @@ final class GroupChatVC: BaseViewController {
|
|||
setupKeyboard()
|
||||
IQKeyboardManager.shared.isEnabled = false
|
||||
IQKeyboardManager.shared.resignOnTouchOutside = false
|
||||
rootView.updateVipMediaAccess(isUnlocked: isVipUnlocked)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
|
|
@ -292,7 +291,6 @@ final class GroupChatVC: BaseViewController {
|
|||
rootView.bringSubviewToFront(rootView.mentionPickerView)
|
||||
rootView.bringSubviewToFront(rootView.quoteBar)
|
||||
rootView.bringSubviewToFront(rootView.bottomBar)
|
||||
rootView.bringSubviewToFront(rootView.disableIMView)
|
||||
rootView.bringSubviewToFront(rootView.voiceRecordView)
|
||||
}
|
||||
|
||||
|
|
@ -580,18 +578,6 @@ final class GroupChatVC: BaseViewController {
|
|||
}
|
||||
|
||||
// MARK: - Actions
|
||||
private var isVipUnlocked: Bool {
|
||||
return AppContextManager.shared.vip > 1
|
||||
}
|
||||
|
||||
private func requireVipForMedia() -> Bool {
|
||||
guard isVipUnlocked else {
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func reactiveAction() {
|
||||
rootView.backBtn.rx.tap
|
||||
.subscribe(onNext: { _ in
|
||||
|
|
@ -641,7 +627,7 @@ final class GroupChatVC: BaseViewController {
|
|||
|
||||
rootView.galleryBtn.rx.tap
|
||||
.subscribe(onNext: { [weak self] in
|
||||
guard let self, self.requireVipForMedia() else { return }
|
||||
guard let self else { return }
|
||||
self.rootView.dismissAllPanels()
|
||||
self.showAlbum()
|
||||
})
|
||||
|
|
@ -649,7 +635,7 @@ final class GroupChatVC: BaseViewController {
|
|||
|
||||
rootView.cameraBtn.rx.tap
|
||||
.subscribe(onNext: { [weak self] in
|
||||
guard let self, self.requireVipForMedia() else { return }
|
||||
guard let self else { return }
|
||||
self.rootView.dismissAllPanels()
|
||||
self.showCamera()
|
||||
})
|
||||
|
|
@ -714,10 +700,9 @@ final class GroupChatVC: BaseViewController {
|
|||
|
||||
// MARK: - 显示相册
|
||||
private func showAlbum() {
|
||||
guard requireVipForMedia() else { return }
|
||||
let picker = PhotoPickerVC(maxCount: 9)
|
||||
picker.onConfirm = { [weak self] images in
|
||||
guard let self, self.requireVipForMedia() else { return }
|
||||
guard let self else { return }
|
||||
for image in images {
|
||||
self.sendImageMessage(image)
|
||||
}
|
||||
|
|
@ -726,7 +711,6 @@ final class GroupChatVC: BaseViewController {
|
|||
}
|
||||
|
||||
private func showCamera() {
|
||||
guard requireVipForMedia() else { return }
|
||||
guard UIImagePickerController.isSourceTypeAvailable(.camera) else {
|
||||
DLToast.showError(text: "当前设备不支持拍照")
|
||||
return
|
||||
|
|
@ -769,11 +753,6 @@ final class GroupChatVC: BaseViewController {
|
|||
private var currentVoiceHover: VoiceRecordView.HoverTarget = .none
|
||||
|
||||
private func enterVoiceInputMode() {
|
||||
guard isVipUnlocked else {
|
||||
rootView.dismissAllPanels()
|
||||
rootView.setVoiceInputMode(true)
|
||||
return
|
||||
}
|
||||
let status = AVCaptureDevice.authorizationStatus(for: .audio)
|
||||
switch status {
|
||||
case .authorized:
|
||||
|
|
@ -857,7 +836,6 @@ final class GroupChatVC: BaseViewController {
|
|||
}
|
||||
|
||||
private func startVoiceRecording() {
|
||||
guard requireVipForMedia() else { return }
|
||||
guard !isVoiceRecording else { return }
|
||||
AudioServicesPlaySystemSound(1519)
|
||||
currentVoiceHover = .none
|
||||
|
|
@ -972,11 +950,6 @@ final class GroupChatVC: BaseViewController {
|
|||
}
|
||||
|
||||
private func sendVoiceMessage(url: URL, durationMs: Int) {
|
||||
guard requireVipForMedia() else {
|
||||
deleteRecordFile()
|
||||
rootView.voiceRecordView.reset()
|
||||
return
|
||||
}
|
||||
GroupIMService.shared.sendGroupMessage(groupId: viewModel.groupId,
|
||||
makeMessage: {
|
||||
OIMMessageInfo.createSoundMessage(fromFullPath: url.path, duration: durationMs)
|
||||
|
|
@ -1235,7 +1208,6 @@ private class MessageListenerProxy: NSObject, OIMAdvancedMsgListener {
|
|||
|
||||
extension GroupChatVC {
|
||||
private func sendImageMessage(_ image: UIImage) {
|
||||
guard requireVipForMedia() else { return }
|
||||
let sendImage = image.cgImage != nil ? image.fixOrientation() : image
|
||||
guard let data = sendImage.jpegData(compressionQuality: 0.8) else { return }
|
||||
let dir = NSTemporaryDirectory()
|
||||
|
|
|
|||
|
|
@ -217,7 +217,6 @@ class GroupChatView: UIView {
|
|||
|
||||
/// 收起所有浮层面板
|
||||
private var isVoiceInputMode = false
|
||||
private var isVipMediaUnlocked = true
|
||||
|
||||
func setVoiceInputMode(_ enabled: Bool) {
|
||||
isVoiceInputMode = enabled
|
||||
|
|
@ -228,18 +227,6 @@ class GroupChatView: UIView {
|
|||
refreshComposer()
|
||||
}
|
||||
|
||||
func updateVipMediaAccess(isUnlocked: Bool) {
|
||||
isVipMediaUnlocked = isUnlocked
|
||||
let lockedBackground = isUnlocked ? nil : UIImage(named: "VipFeature/media_locked_bg")
|
||||
galleryBtn.setBackgroundImage(lockedBackground, for: .normal)
|
||||
cameraBtn.setBackgroundImage(lockedBackground, for: .normal)
|
||||
galleryBtn.backgroundColor = isUnlocked ? .white : .clear
|
||||
cameraBtn.backgroundColor = isUnlocked ? .white : .clear
|
||||
voiceBtn.setImage(UIImage(named: "IM/voice_input"), for: .normal)
|
||||
textField.placeholder = "发消息或者按住说话"
|
||||
refreshComposer()
|
||||
}
|
||||
|
||||
func refreshComposer() {
|
||||
let hasText = !(textField.text ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
textField.updatePlaceholderVisibility()
|
||||
|
|
@ -292,9 +279,7 @@ class GroupChatView: UIView {
|
|||
private func updateHoldSpeakAppearance() {
|
||||
var configuration = UIButton.Configuration.plain()
|
||||
configuration.title = "按住 说话"
|
||||
configuration.image = isVipMediaUnlocked ? nil : UIImage(named: "VipFeature/crown")
|
||||
configuration.imagePlacement = .leading
|
||||
configuration.imagePadding = isVipMediaUnlocked ? 0 : 10
|
||||
configuration.contentInsets = .zero
|
||||
configuration.baseForegroundColor = UIColor(hexStr: "#293445")
|
||||
configuration.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { attributes in
|
||||
|
|
@ -389,7 +374,6 @@ class GroupChatView: UIView {
|
|||
addSubview(mentionPickerView)
|
||||
addSubview(quickActionBar)
|
||||
addSubview(bottomBar)
|
||||
addSubview(disableIMView)
|
||||
bottomBar.addSubview(bottomBarCornerView)
|
||||
addSubview(voiceRecordView)
|
||||
addSubview(emojiPanelView)
|
||||
|
|
@ -522,11 +506,6 @@ class GroupChatView: UIView {
|
|||
bottomBarHeightConstraint = bottomBar.heightAnchor.constraint(equalToConstant: minimumBottomBarHeight)
|
||||
bottomBarHeightConstraint?.isActive = true
|
||||
|
||||
disableIMView.layoutChain
|
||||
.edgesHorzontal(15)
|
||||
.height(50)
|
||||
.bottom(kSafeBottomMargin)
|
||||
|
||||
bottomBarBottomConstraint = bottomBar.jh_constraint(.bottom, toAttribute: .bottom, otherView: bottomBar.superview, relation: .equal)
|
||||
|
||||
bottomBarCornerView.layoutChain.edges()
|
||||
|
|
@ -883,32 +862,6 @@ class GroupChatView: UIView {
|
|||
return btn
|
||||
}()
|
||||
|
||||
/// IM功能禁用
|
||||
lazy var disableIMView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
view.layer.shadowColor = UIColor(hexStr: "#0F2846", alpha: 0.1).cgColor
|
||||
view.layer.shadowOffset = CGSize(width: 0, height: 0)
|
||||
view.layer.shadowOpacity = 1
|
||||
view.layer.shadowRadius = 9
|
||||
view.isHidden = true
|
||||
|
||||
let v = UIView()
|
||||
v.backgroundColor = .white
|
||||
v.cornerRadius = 25
|
||||
view.addSubview(v)
|
||||
v.layoutChain.edges()
|
||||
|
||||
let label = UILabel()
|
||||
label.text = "开通VIP后可发送消息"
|
||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
||||
label.font = .systemFont(ofSize: 15, weight: .medium)
|
||||
view.addSubview(label)
|
||||
label.layoutChain.centerX().centerY()
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
/// 语音面板
|
||||
lazy var voiceRecordView: VoiceRecordView = {
|
||||
let v = VoiceRecordView()
|
||||
|
|
|
|||
|
|
@ -264,7 +264,6 @@ final class GroupMemberListCell: UICollectionViewCell {
|
|||
nameLabel.text = model.showName
|
||||
nameLabel.textColor = UIColor(hexStr: isCurrentUser ? "#16B3FF" : "#0F2846")
|
||||
selectionImageView.isHidden = !isSelected
|
||||
vipBackgroundView.isHidden = !model.shouldShowVipAvatarFrame
|
||||
|
||||
let isSOS = model.status == 1
|
||||
updateSOSBlink(isSOS)
|
||||
|
|
@ -291,10 +290,9 @@ final class GroupMemberListCell: UICollectionViewCell {
|
|||
contentView.addSubview(avatarImageView)
|
||||
contentView.addSubview(bubbleImageView)
|
||||
contentView.addSubview(sosImageView)
|
||||
contentView.addSubview(vipBackgroundView)
|
||||
contentView.addSubview(nameLabel)
|
||||
|
||||
[selectionImageView, avatarImageView, bubbleImageView, sosImageView, vipBackgroundView, nameLabel].forEach {
|
||||
[selectionImageView, avatarImageView, bubbleImageView, sosImageView, nameLabel].forEach {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
|
||||
|
|
@ -319,9 +317,6 @@ final class GroupMemberListCell: UICollectionViewCell {
|
|||
sosImageView.trailingAnchor.constraint(equalTo: avatarImageView.trailingAnchor),
|
||||
sosImageView.bottomAnchor.constraint(equalTo: avatarImageView.bottomAnchor),
|
||||
|
||||
vipBackgroundView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 7),
|
||||
vipBackgroundView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||||
|
||||
nameLabel.topAnchor.constraint(equalTo: avatarImageView.bottomAnchor, constant: 7),
|
||||
nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
nameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
|
||||
|
|
@ -360,12 +355,6 @@ final class GroupMemberListCell: UICollectionViewCell {
|
|||
return view
|
||||
}()
|
||||
|
||||
private lazy var vipBackgroundView: UIImageView = {
|
||||
let view = UIImageView(image: UIImage(named: "GroupMemberList/vip"))
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var nameLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 12, weight: .medium)
|
||||
|
|
|
|||
|
|
@ -1,120 +0,0 @@
|
|||
//
|
||||
// CreateBubblePopView.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/26.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
|
||||
class CreateBubblePopView: UIView {
|
||||
|
||||
private static let shared = CreateBubblePopView(frame: CGRect(origin: .zero, size: kScreenSize))
|
||||
|
||||
var disposeBag = DisposeBag()
|
||||
|
||||
static func show() {
|
||||
guard let superView = kKeyWindow else {
|
||||
return
|
||||
}
|
||||
|
||||
if CreateBubblePopView.shared.superview != nil {
|
||||
CreateBubblePopView.shared.removeFromSuperview()
|
||||
CreateBubblePopView.shared.bgView.frame = .zero
|
||||
}
|
||||
CreateBubblePopView.shared.bgView.alpha = 1
|
||||
CreateBubblePopView.shared.bgView.frame = CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight)
|
||||
superView.addSubview(CreateBubblePopView.shared)
|
||||
superView.bringSubviewToFront(CreateBubblePopView.shared)
|
||||
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
CreateBubblePopView.shared.bgView.alpha = 1
|
||||
}
|
||||
}
|
||||
|
||||
/// 关闭
|
||||
static func dismiss() {
|
||||
guard CreateBubblePopView.shared.superview != nil else { return }
|
||||
|
||||
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseIn]) {
|
||||
CreateBubblePopView.shared.bgView.alpha = 0
|
||||
} completion: { _ in
|
||||
CreateBubblePopView.shared.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
private func setupRx() {
|
||||
upgradedBtn.rx.tap.subscribe(onNext: { _ in
|
||||
CreateBubblePopView.dismiss()
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
closeBtn.rx.tap.subscribe(onNext: { _ in
|
||||
CreateBubblePopView.dismiss()
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private lazy var bgView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .black.withAlphaComponent(0.5)
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var vipImgView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Bubble/vip_pop")
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var upgradedBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.backgroundColor = .clear
|
||||
btn.setBackgroundImage(UIImage(named: "Bubble/upgrade_bg"), for: .normal)
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var closeBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.backgroundColor = .clear
|
||||
btn.setBackgroundImage(UIImage(named: "Group/close"), for: .normal)
|
||||
btn.extendEdgeInsets = UIEdgeInsets(top: 10, left: 100, bottom: 100, right: 100)
|
||||
return btn
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .clear
|
||||
addSubview(bgView)
|
||||
bgView.addSubview(vipImgView)
|
||||
bgView.addSubview(upgradedBtn)
|
||||
bgView.addSubview(closeBtn)
|
||||
|
||||
vipImgView.layoutChain
|
||||
.centerY()
|
||||
.edgesHorzontal(25)
|
||||
.heightToWidth(814/648)
|
||||
|
||||
upgradedBtn.layoutChain
|
||||
.topToBottomOfView(vipImgView, offset: -20)
|
||||
.centerX()
|
||||
.width(240)
|
||||
.height(60)
|
||||
|
||||
closeBtn.layoutChain
|
||||
.topToBottomOfView(upgradedBtn, offset: 15)
|
||||
.centerX()
|
||||
.width(22)
|
||||
.height(22)
|
||||
|
||||
setupRx()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -25,7 +25,6 @@ final class CreateBubbleSetupView: UIView {
|
|||
private let subtitleLab = UILabel()
|
||||
private let titleLab = UILabel()
|
||||
private let hourPicker = BubbleHourPickerView()
|
||||
private let vipOfferImageView = UIImageView(image: UIImage(named: "VipFeature/limited_offer"))
|
||||
|
||||
private let sheetOverlay = UIView()
|
||||
private let sheetPanel = UIView()
|
||||
|
|
@ -84,7 +83,6 @@ final class CreateBubbleSetupView: UIView {
|
|||
addSubview(subtitleLab)
|
||||
addSubview(hourPicker)
|
||||
addSubview(confirmBtn)
|
||||
addSubview(vipOfferImageView)
|
||||
|
||||
heroView.showsOuterDecorBubbles = false
|
||||
heroView.layoutChain
|
||||
|
|
@ -115,25 +113,6 @@ final class CreateBubbleSetupView: UIView {
|
|||
confirmBtn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||||
confirmBtn.cornerRadius = 20
|
||||
confirmBtn.layoutChain.left(30).right(30).bottom(34).height(56)
|
||||
|
||||
vipOfferImageView.contentMode = .scaleAspectFit
|
||||
vipOfferImageView.isHidden = true
|
||||
vipOfferImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
vipOfferImageView.trailingAnchor.constraint(equalTo: confirmBtn.trailingAnchor, constant: -6),
|
||||
vipOfferImageView.bottomAnchor.constraint(equalTo: confirmBtn.topAnchor, constant: 10),
|
||||
vipOfferImageView.widthAnchor.constraint(equalToConstant: 94),
|
||||
vipOfferImageView.heightAnchor.constraint(equalToConstant: 32)
|
||||
])
|
||||
}
|
||||
|
||||
func updateVipAccess(isUnlocked: Bool) {
|
||||
confirmBtn.setTitle(isUnlocked ? "确定" : "开通会员 解锁功能", for: .normal)
|
||||
confirmBtn.setBackgroundImage(
|
||||
UIImage(named: isUnlocked ? "Common/button_bg_2" : "VipFeature/wide_button_bg"),
|
||||
for: .normal
|
||||
)
|
||||
vipOfferImageView.isHidden = isUnlocked
|
||||
}
|
||||
|
||||
private func setupSheet() {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ class CreateBubbleVC: BaseViewController {
|
|||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
rootView.updateVipAccess(isUnlocked: isVipUnlocked)
|
||||
requestUserStatus()
|
||||
}
|
||||
|
||||
|
|
@ -49,10 +48,6 @@ class CreateBubbleVC: BaseViewController {
|
|||
rootView.setupView.confirmBtn.rx.tap
|
||||
.subscribe(onNext: { [weak self] in
|
||||
guard let self else { return }
|
||||
guard self.isVipUnlocked else {
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
return
|
||||
}
|
||||
let hours = self.rootView.setupView.selectedHour.value
|
||||
let message = self.rootView.setupView.buildMessage(for: hours)
|
||||
self.rootView.setupView.showNotifySheet(message: message)
|
||||
|
|
@ -85,19 +80,10 @@ class CreateBubbleVC: BaseViewController {
|
|||
}
|
||||
|
||||
private func submitCreate() {
|
||||
guard isVipUnlocked else {
|
||||
rootView.setupView.dismissNotifySheet()
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
return
|
||||
}
|
||||
let hours = rootView.setupView.selectedHour.value
|
||||
requestSetBubble(enable: true, keep_time: hours)
|
||||
}
|
||||
|
||||
private var isVipUnlocked: Bool {
|
||||
return AppContextManager.shared.vip > 1
|
||||
}
|
||||
|
||||
private func restoreCachedBubbleIfNeeded() {
|
||||
guard AppContextManager.shared.status == 2,
|
||||
let endDate = Defaults[\.bubbleKeepTimes],
|
||||
|
|
|
|||
|
|
@ -90,8 +90,4 @@ class CreateBubbleView: UIView {
|
|||
func updateNavTitleForSheet(_ visible: Bool) {
|
||||
navView.titleLabel.text = visible ? "通知您的圈子" : "设置气泡时间"
|
||||
}
|
||||
|
||||
func updateVipAccess(isUnlocked: Bool) {
|
||||
setupView.updateVipAccess(isUnlocked: isUnlocked)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,478 +0,0 @@
|
|||
//
|
||||
// GroupMemberView.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/5/29.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
import Lottie
|
||||
|
||||
class GroupMemberView: UIView {
|
||||
|
||||
func setupCountData(_ memberCount: Int, _ memberOnlineCount: Int) {
|
||||
groupContentLab.text = "共\(memberCount)成员/\(memberOnlineCount)在线"
|
||||
}
|
||||
|
||||
private func setupRx() {
|
||||
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
addSubview(headerBgView)
|
||||
addSubview(bgView)
|
||||
addSubview(groupIcon)
|
||||
addSubview(titleIcon)
|
||||
addSubview(groupContentLab)
|
||||
addSubview(refreshBtn)
|
||||
addSubview(inviteJoinBtn)
|
||||
addSubview(tableView)
|
||||
|
||||
headerBgView.layoutChain
|
||||
.edges(excludingEdge: .bottom)
|
||||
.heightToWidth(136/375)
|
||||
|
||||
bgView.layoutChain
|
||||
.topToBottomOfView(headerBgView)
|
||||
.edges(excludingEdge: .top)
|
||||
|
||||
groupIcon.layoutChain
|
||||
.top()
|
||||
.left()
|
||||
.width(52)
|
||||
.height(52)
|
||||
|
||||
titleIcon.layoutChain
|
||||
.top(5)
|
||||
.leftToRightOfView(groupIcon)
|
||||
.width(110)
|
||||
.height(26)
|
||||
|
||||
groupContentLab.layoutChain
|
||||
.topToBottomOfView(titleIcon, offset: 8)
|
||||
.leftToView(titleIcon)
|
||||
|
||||
inviteJoinBtn.layoutChain
|
||||
.top(23)
|
||||
.right(15)
|
||||
.height(20)
|
||||
|
||||
refreshBtn.layoutChain
|
||||
.topToView(inviteJoinBtn)
|
||||
.rightToLeftOfView(inviteJoinBtn, offset: -20)
|
||||
.height(20)
|
||||
|
||||
inviteJoinBtn.sizeToFit()
|
||||
refreshBtn.sizeToFit()
|
||||
|
||||
tableView.layoutChain
|
||||
.topToBottomOfView(groupContentLab, offset: 8)
|
||||
.edges(excludingEdge: .top)
|
||||
}
|
||||
|
||||
lazy var headerBgView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Home/group_member_bg")
|
||||
view.backgroundColor = .clear
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var bgView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .white
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var groupIcon: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Home/group_icon")
|
||||
view.backgroundColor = .clear
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var titleIcon: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Home/title_icon")
|
||||
view.backgroundColor = .clear
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var groupContentLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = "共0成员/0在线"
|
||||
label.font = .systemFont(ofSize: 12, weight: .medium)
|
||||
label.textColor = UIColor(hexStr: "#0F2846")
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var refreshBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setTitle(" 刷新列表", for: .normal)
|
||||
btn.setTitleColor(UIColor(hexStr: "#1A1A1A"), for: .normal)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .regular)
|
||||
btn.setImage(UIImage(named: "Home/refresh"), for: .normal)
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var inviteJoinBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setTitle(" 邀请加入", for: .normal)
|
||||
btn.setTitleColor(UIColor(hexStr: "#1A1A1A"), for: .normal)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .regular)
|
||||
btn.setImage(UIImage(named: "Home/member"), for: .normal)
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var tableView: UITableView = {
|
||||
let tableView = UITableView(frame: .zero, style: .plain)
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.separatorStyle = .none
|
||||
tableView.estimatedRowHeight = 76
|
||||
tableView.showsVerticalScrollIndicator = false
|
||||
tableView.register(GroupMemberCell.self)
|
||||
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
|
||||
return tableView
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .clear
|
||||
setupUI()
|
||||
setupRx()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - GroupMemberCell
|
||||
class GroupMemberCell: UITableViewCell {
|
||||
|
||||
func configure(model: GroupMemberModel, isCurrentUser: Bool, isOwn: Bool) {
|
||||
ownView.isHidden = !isOwn
|
||||
avaterImgView.setHeadPic(model.displayHeadPic)
|
||||
nameLab.text = model.showName
|
||||
// 位置
|
||||
locationLab.text = "在 " + model.lastLocation
|
||||
|
||||
nameLab.textColor = UIColor(hexStr: isCurrentUser ? "#16B3FF" : "#0F2846")
|
||||
|
||||
// 会员权益
|
||||
batteryInfoView.isHidden = true
|
||||
if AppContextManager.shared.vip > 1, model.is_online {
|
||||
batteryInfoView.isHidden = model.battery.int == 0
|
||||
// 电量 16是电池图标宽度,右边有电池造型需要减去
|
||||
let batteryPercent = min(CGFloat(model.battery.int), 100)
|
||||
batteryView.layoutChain.width(CGFloat(16 - 1) * batteryPercent / 100.0)
|
||||
batteryLab.text = "\(model.battery)%"
|
||||
}
|
||||
|
||||
// 在线状态
|
||||
if model.is_online {
|
||||
onlineDotView.isHidden = false
|
||||
onlineStatusLab.text = "在线"
|
||||
onlineStatusLab.textColor = ThemeManager.shared.color.titleAuxColor
|
||||
} else {
|
||||
onlineDotView.isHidden = true
|
||||
onlineStatusLab.text = model.lastUpdateText
|
||||
onlineStatusLab.textColor = UIColor(hexStr: "#999999")
|
||||
}
|
||||
|
||||
// sos
|
||||
sosLottieView.isHidden = model.status != 1
|
||||
model.status == 1 ? sosLottieView.play() : sosLottieView.stop()
|
||||
|
||||
// 气泡
|
||||
shieldIconView.isHidden = model.status != 2
|
||||
}
|
||||
|
||||
private func setupSubviews() {
|
||||
contentView.addSubview(selectedBgView)
|
||||
contentView.addSubview(avaterImgView)
|
||||
contentView.addSubview(batteryInfoView)
|
||||
batteryInfoView.addSubview(cornerView)
|
||||
cornerView.addSubview(batteryView)
|
||||
cornerView.addSubview(batteryIcon)
|
||||
cornerView.addSubview(batteryLab)
|
||||
contentView.addSubview(nameLab)
|
||||
contentView.addSubview(locationIcon)
|
||||
contentView.addSubview(locationLab)
|
||||
contentView.addSubview(tagView)
|
||||
contentView.addSubview(onlineView)
|
||||
|
||||
setupLayout()
|
||||
}
|
||||
|
||||
private func setupLayout() {
|
||||
selectedBgView.layoutChain
|
||||
.edgesHorzontal(15)
|
||||
.edgesVertical()
|
||||
|
||||
avaterImgView.layoutChain
|
||||
.edgesVertical(13)
|
||||
.left(28)
|
||||
.width(50)
|
||||
.height(50)
|
||||
|
||||
batteryInfoView.layoutChain
|
||||
.leftToView(avaterImgView)
|
||||
.rightToView(avaterImgView)
|
||||
.bottomToView(avaterImgView)
|
||||
.height(12)
|
||||
|
||||
cornerView.layoutChain.edges()
|
||||
|
||||
batteryIcon.layoutChain
|
||||
.left(7)
|
||||
.centerY()
|
||||
.width(16)
|
||||
.height(8)
|
||||
|
||||
batteryView.layoutChain
|
||||
.topToView(batteryIcon)
|
||||
.leftToView(batteryIcon, offset: -1)
|
||||
.bottomToView(batteryIcon)
|
||||
|
||||
batteryLab.layoutChain
|
||||
.leftToRightOfView(batteryIcon, offset: 4)
|
||||
.right(5)
|
||||
.centerY()
|
||||
|
||||
nameLab.layoutChain
|
||||
.topToView(avaterImgView, offset: 6)
|
||||
.leftToRightOfView(avaterImgView, offset: 8)
|
||||
|
||||
locationLab.layoutChain
|
||||
.bottomToView(avaterImgView, offset: -6)
|
||||
.leftToView(nameLab)
|
||||
|
||||
locationIcon.layoutChain
|
||||
.leftToRightOfView(locationLab, offset: 4)
|
||||
.centerY(locationLab)
|
||||
.width(10)
|
||||
.height(10)
|
||||
.right(80, relation: .greaterThanOrEqual)
|
||||
|
||||
tagView.layoutChain
|
||||
.leftToRightOfView(nameLab, offset: 4)
|
||||
.centerY(nameLab)
|
||||
.height(30)
|
||||
|
||||
ownView.layoutChain.height(15)
|
||||
|
||||
offlineView.layoutChain.height(15)
|
||||
|
||||
onlineView.layoutChain
|
||||
.right(26)
|
||||
.bottom(18)
|
||||
}
|
||||
|
||||
lazy var selectedBgView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = UIColor(hexStr: "#EAFBFF")
|
||||
view.cornerRadius = 10
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var avaterImgView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.backgroundColor = .lightGray
|
||||
view.contentMode = .scaleAspectFill
|
||||
view.cornerRadius = 25
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var batteryInfoView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
view.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.1).cgColor
|
||||
view.layer.shadowOffset = CGSize(width: 0, height: 2)
|
||||
view.layer.shadowOpacity = 1
|
||||
view.layer.shadowRadius = 6
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var cornerView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .white
|
||||
view.cornerRadius = 6
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var batteryView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = UIColor(hexStr: "#75E582")
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var batteryIcon: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.backgroundColor = .clear
|
||||
view.image = UIImage(named: "Home/battery")
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var batteryLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = UIColor(hexStr: "#D4D4D4")
|
||||
label.font = .systemFont(ofSize: 6, weight: .medium)
|
||||
return label
|
||||
}()
|
||||
|
||||
|
||||
lazy var nameLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = UIColor(hexStr: "#0F2846")
|
||||
label.font = .systemFont(ofSize: 14, weight: .semibold)
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var locationIcon: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Home/member_location")
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var locationLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = UIColor(hexStr: "#8D8D8D")
|
||||
label.font = .systemFont(ofSize: 10, weight: .regular)
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var tagView: UIStackView = {
|
||||
let view = UIStackView(arrangedSubviews: [ownView, offlineView, sosLottieView, shieldIconView])
|
||||
view.axis = .horizontal
|
||||
view.alignment = .center
|
||||
view.spacing = 5
|
||||
view.backgroundColor = .clear
|
||||
return view
|
||||
}()
|
||||
|
||||
// 圈主
|
||||
lazy var ownView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = UIColor(hexStr: "#9FD9FF")
|
||||
view.cornerRadius = 2
|
||||
|
||||
let label = UILabel()
|
||||
label.textColor = UIColor(hexStr: "#194045")
|
||||
label.font = .systemFont(ofSize: 8, weight: .medium)
|
||||
label.text = "圈主"
|
||||
|
||||
view.addSubview(label)
|
||||
label.layoutChain
|
||||
.edgesVertical(2)
|
||||
.edgesHorzontal(8)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
// 离线
|
||||
lazy var offlineView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = UIColor(hexStr: "#EDEDED")
|
||||
view.cornerRadius = 2
|
||||
|
||||
let label = UILabel()
|
||||
label.textColor = ThemeManager.shared.color.contentColor
|
||||
label.font = .systemFont(ofSize: 8, weight: .medium)
|
||||
label.text = "离线"
|
||||
|
||||
view.addSubview(label)
|
||||
label.layoutChain
|
||||
.edgesVertical(2)
|
||||
.edgesHorzontal(8)
|
||||
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
// sos
|
||||
lazy var sosLottieView: LottieAnimationView = {
|
||||
let view = LottieAnimationView(name: "siren")
|
||||
view.loopMode = .loop
|
||||
view.isHidden = true
|
||||
|
||||
view.layoutChain.width(26).height(26)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
// 气泡(盾牌)
|
||||
lazy var shieldIconView: UIImageView = {
|
||||
let view = UIImageView(image: UIImage(named: "Home/shield"))
|
||||
view.contentMode = .scaleAspectFill
|
||||
view.isHidden = true
|
||||
|
||||
view.layoutChain.width(20).height(20)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
// 在线
|
||||
lazy var onlineView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
view.addSubview(onlineDotView)
|
||||
view.addSubview(onlineStatusLab)
|
||||
|
||||
onlineDotView.layoutChain
|
||||
.left()
|
||||
.centerY()
|
||||
.height(8)
|
||||
.width(8)
|
||||
|
||||
onlineStatusLab.layoutChain
|
||||
.edgesVertical()
|
||||
.leftToRightOfView(onlineDotView, offset: 3)
|
||||
.right()
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var onlineDotView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = UIColor(hexStr: "#67EA76")
|
||||
view.cornerRadius = 4
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var onlineStatusLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
||||
label.font = .systemFont(ofSize: 10, weight: .medium)
|
||||
label.text = "在线"
|
||||
return label
|
||||
}()
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
// Initialization code
|
||||
}
|
||||
|
||||
override init(style: CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
super.setSelected(selected, animated: animated)
|
||||
selectedBgView.isHidden = !selected
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@ import SwiftyUserDefaults
|
|||
|
||||
enum MemberFeatureAccessState: Equatable {
|
||||
case available
|
||||
case vipRequired
|
||||
case privilegeDenied
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +27,6 @@ class GroupMemberView2: UIView {
|
|||
private var appUsageBottomConstraint: NSLayoutConstraint?
|
||||
private var isAppUsageVisible = false
|
||||
private var currentMemberModel: GroupMemberModel?
|
||||
private var currentGroupVip = GroupMemberVip()
|
||||
private var emojiEchoByUserId: [String: String] = [:]
|
||||
private var messageEchoByUserId: [String: String] = [:]
|
||||
private var phoneStatusObserver: NSObjectProtocol?
|
||||
|
|
@ -37,7 +35,6 @@ class GroupMemberView2: UIView {
|
|||
private var localWeatherSnapshotKey: String?
|
||||
private var localWeatherUpdatedAt: Date?
|
||||
private var pendingWeatherLocationKey: String?
|
||||
private(set) var isTodayTrackVipRequired = false
|
||||
var onCoupleMemberTap: ((GroupMemberModel) -> Void)?
|
||||
|
||||
private static let weatherRefreshInterval: TimeInterval = 15 * 60
|
||||
|
|
@ -55,11 +52,9 @@ class GroupMemberView2: UIView {
|
|||
func setupMemberInfo(
|
||||
_ model: GroupMemberModel,
|
||||
isOwner: Bool,
|
||||
phoneUsage: PhoneUsageTodayModel?,
|
||||
groupVip: GroupMemberVip
|
||||
phoneUsage: PhoneUsageTodayModel?
|
||||
) {
|
||||
currentMemberModel = model
|
||||
currentGroupVip = groupVip
|
||||
memberNameLab.text = model.showName
|
||||
ownView.isHidden = !isOwner
|
||||
relationIconView.configure(relationIdx: model.extra.relation_idx)
|
||||
|
|
@ -78,7 +73,7 @@ class GroupMemberView2: UIView {
|
|||
}
|
||||
|
||||
applyPhoneUsage(phoneUsage)
|
||||
if isCurrentUser, !isPhoneDeviceVipRequired {
|
||||
if isCurrentUser {
|
||||
requestLocalWeather()
|
||||
}
|
||||
updateRealtimeInteractionVisibility(isCurrentUser: isCurrentUser)
|
||||
|
|
@ -87,7 +82,6 @@ class GroupMemberView2: UIView {
|
|||
|
||||
func clearMemberInfo() {
|
||||
currentMemberModel = nil
|
||||
currentGroupVip = GroupMemberVip()
|
||||
memberNameLab.text = ""
|
||||
ownView.isHidden = true
|
||||
relationIconView.configure(relationIdx: "")
|
||||
|
|
@ -105,20 +99,18 @@ class GroupMemberView2: UIView {
|
|||
func updatePhoneUsage(_ phoneUsage: PhoneUsageTodayModel?, for userId: String) {
|
||||
guard currentMemberModel?.user_id == userId else { return }
|
||||
applyPhoneUsage(phoneUsage)
|
||||
if Self.isCurrentUser(userId), !isPhoneDeviceVipRequired {
|
||||
if Self.isCurrentUser(userId) {
|
||||
applyLocalDeviceInfo()
|
||||
}
|
||||
}
|
||||
|
||||
func refreshLocalDeviceInfoIfNeeded() {
|
||||
guard isShowingCurrentUser, !isPhoneDeviceVipRequired else { return }
|
||||
guard isShowingCurrentUser else { return }
|
||||
applyLocalDeviceInfo()
|
||||
}
|
||||
|
||||
func refreshInterceptState() {
|
||||
memberCV.reloadData()
|
||||
coupleLeftSlot.refreshVipVisibility()
|
||||
coupleRightSlot.refreshVipVisibility()
|
||||
updateFeatureVisibilityLayout()
|
||||
}
|
||||
|
||||
|
|
@ -206,9 +198,7 @@ class GroupMemberView2: UIView {
|
|||
}
|
||||
|
||||
private func applyPhoneUsage(_ usage: PhoneUsageTodayModel?) {
|
||||
if isPhoneDeviceVipRequired {
|
||||
stopLocalPhoneStatusObserving()
|
||||
} else if isShowingCurrentUser {
|
||||
if isShowingCurrentUser {
|
||||
applyLocalDeviceInfo()
|
||||
startLocalPhoneStatusObserving()
|
||||
} else {
|
||||
|
|
@ -219,7 +209,6 @@ class GroupMemberView2: UIView {
|
|||
applyDeviceInfo(usage?.phoneInfo)
|
||||
}
|
||||
}
|
||||
applyPhoneDeviceAccessState()
|
||||
if isShowingOfflineOther {
|
||||
phoneReportView.configure(with: nil)
|
||||
appUsageView.configure(with: nil)
|
||||
|
|
@ -236,15 +225,11 @@ class GroupMemberView2: UIView {
|
|||
|
||||
var isPhoneReportDenied: Bool { phoneReportView.isDenied }
|
||||
var isAppUsageDenied: Bool { appUsageView.isDenied }
|
||||
var isPhoneReportVipRequired: Bool { phoneReportView.isVipRequired }
|
||||
var isAppUsageVipRequired: Bool { appUsageView.isVipRequired }
|
||||
var onPhoneReportRemind: (() -> Void)?
|
||||
var onAppUsageRemind: (() -> Void)?
|
||||
|
||||
private func applyFeatureAccessStates(appUsagePreview: MemberAppUsagePreview?) {
|
||||
guard currentMemberModel != nil else {
|
||||
isTodayTrackVipRequired = false
|
||||
todayTrackVipView.isHidden = true
|
||||
phoneReportView.setAccessState(.available)
|
||||
appUsageView.setAccessState(.available)
|
||||
updateAppUsageVisibility(appUsagePreview != nil)
|
||||
|
|
@ -253,16 +238,12 @@ class GroupMemberView2: UIView {
|
|||
let isSelf = isShowingCurrentUser
|
||||
let memberPrivilege = currentMemberModel?.extra.privilege ?? GroupPrivilege()
|
||||
let phoneState = Self.accessState(
|
||||
vipAllowed: currentGroupVip.phone_report,
|
||||
privilegeAllowed: isSelf || memberPrivilege.phone_report
|
||||
)
|
||||
let appUsageState = Self.accessState(
|
||||
vipAllowed: currentGroupVip.app_usage_record,
|
||||
privilegeAllowed: isSelf || memberPrivilege.app_usage_record
|
||||
)
|
||||
|
||||
isTodayTrackVipRequired = !isSelf && currentGroupVip.trip_analysis_others == false
|
||||
todayTrackVipView.isHidden = !isTodayTrackVipRequired
|
||||
phoneReportView.setAccessState(phoneState)
|
||||
phoneReportView.onDeniedAction = { [weak self] in self?.onPhoneReportRemind?() }
|
||||
appUsageView.setAccessState(appUsageState)
|
||||
|
|
@ -274,13 +255,7 @@ class GroupMemberView2: UIView {
|
|||
updateAppUsageVisibility(showUsage)
|
||||
}
|
||||
|
||||
private static func accessState(
|
||||
vipAllowed: Bool?,
|
||||
privilegeAllowed: Bool
|
||||
) -> MemberFeatureAccessState {
|
||||
if vipAllowed == false {
|
||||
return .vipRequired
|
||||
}
|
||||
private static func accessState(privilegeAllowed: Bool) -> MemberFeatureAccessState {
|
||||
if !privilegeAllowed {
|
||||
return .privilegeDenied
|
||||
}
|
||||
|
|
@ -291,23 +266,6 @@ class GroupMemberView2: UIView {
|
|||
Self.isCurrentUser(currentMemberModel?.user_id)
|
||||
}
|
||||
|
||||
private var isPhoneDeviceVipRequired: Bool {
|
||||
currentMemberModel != nil && !isShowingCurrentUser && currentGroupVip.view_phone == false
|
||||
}
|
||||
|
||||
private func applyPhoneDeviceAccessState() {
|
||||
let requiresVip = isPhoneDeviceVipRequired
|
||||
phoneModelView.isHidden = requiresVip
|
||||
batteryView.isHidden = requiresVip
|
||||
networkView.isHidden = requiresVip
|
||||
weatherView.isHidden = requiresVip
|
||||
phoneDeviceVipButton.isHidden = !requiresVip
|
||||
}
|
||||
|
||||
@objc private func handlePhoneDeviceVipTap() {
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
}
|
||||
|
||||
private static func isCurrentUser(_ userId: String?) -> Bool {
|
||||
let memberId = userId?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
let selfId = AppContextManager.shared.userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
|
@ -346,7 +304,7 @@ class GroupMemberView2: UIView {
|
|||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
guard let self, self.isShowingCurrentUser, !self.isPhoneDeviceVipRequired else { return }
|
||||
guard let self, self.isShowingCurrentUser else { return }
|
||||
self.applyLocalDeviceInfo()
|
||||
}
|
||||
}
|
||||
|
|
@ -375,7 +333,6 @@ class GroupMemberView2: UIView {
|
|||
}
|
||||
|
||||
private func requestLocalWeather() {
|
||||
guard !isPhoneDeviceVipRequired else { return }
|
||||
guard let latitude = Defaults[\.currentLatitude],
|
||||
let longitude = Defaults[\.currentLongitude] else {
|
||||
if localWeatherSnapshot == nil {
|
||||
|
|
@ -409,7 +366,7 @@ class GroupMemberView2: UIView {
|
|||
MemberWeatherService.shared.weather(for: location) { [weak self] result in
|
||||
guard let self, self.weatherRequestID == requestID else { return }
|
||||
self.pendingWeatherLocationKey = nil
|
||||
guard self.isShowingCurrentUser, !self.isPhoneDeviceVipRequired else { return }
|
||||
guard self.isShowingCurrentUser else { return }
|
||||
switch result {
|
||||
case .success(let snapshot):
|
||||
self.localWeatherSnapshot = snapshot
|
||||
|
|
@ -442,15 +399,16 @@ class GroupMemberView2: UIView {
|
|||
}
|
||||
|
||||
private func updateFeatureVisibilityLayout() {
|
||||
let hidesPhoneReport = AppContextManager.shared.systemConfig?.isIntercept == true
|
||||
let hidesPhoneReport = false
|
||||
let showsAppUsage = !hidesPhoneReport && isAppUsageVisible
|
||||
phoneReportView.isHidden = hidesPhoneReport
|
||||
appUsageView.isHidden = hidesPhoneReport
|
||||
appUsageView.isHidden = !showsAppUsage
|
||||
|
||||
appUsageTopToPhoneReportConstraint?.isActive = !hidesPhoneReport
|
||||
appUsageTopToTodayTrackConstraint?.isActive = hidesPhoneReport
|
||||
phoneReportBottomConstraint?.isActive = !hidesPhoneReport && !isAppUsageVisible
|
||||
appUsageBottomConstraint?.isActive = isAppUsageVisible
|
||||
todayTrackBottomConstraint?.isActive = hidesPhoneReport && !isAppUsageVisible
|
||||
appUsageBottomConstraint?.isActive = showsAppUsage
|
||||
todayTrackBottomConstraint?.isActive = hidesPhoneReport
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
|
|
@ -950,7 +908,6 @@ class GroupMemberView2: UIView {
|
|||
view.addSubview(batteryView)
|
||||
view.addSubview(networkView)
|
||||
view.addSubview(weatherView)
|
||||
view.addSubview(phoneDeviceVipButton)
|
||||
|
||||
phoneModelView.layoutChain
|
||||
.topToBottomOfView(locationLab, offset: 20)
|
||||
|
|
@ -972,12 +929,6 @@ class GroupMemberView2: UIView {
|
|||
.right()
|
||||
.widthToView(phoneModelView)
|
||||
|
||||
phoneDeviceVipButton.layoutChain
|
||||
.centerX()
|
||||
.centerY(phoneModelView)
|
||||
.width(174)
|
||||
.height(34)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
|
|
@ -1240,17 +1191,6 @@ class GroupMemberView2: UIView {
|
|||
return label
|
||||
}()
|
||||
|
||||
private lazy var phoneDeviceVipButton: UIButton = {
|
||||
let button = UIButton(type: .custom)
|
||||
button.setTitle("开通会员 解锁功能", for: .normal)
|
||||
button.setTitleColor(.white, for: .normal)
|
||||
button.titleLabel?.font = FontManager.boboBold(14)
|
||||
button.setBackgroundImage(UIImage(named: "VipFeature/narrow_button_bg"), for: .normal)
|
||||
button.isHidden = true
|
||||
button.addTarget(self, action: #selector(handlePhoneDeviceVipTap), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
|
||||
// MARK: - 手机报告 / APP使用记录
|
||||
lazy var phoneReportView = MemberPhoneReportView()
|
||||
lazy var appUsageView: MemberAppUsageView = {
|
||||
|
|
@ -1464,25 +1404,6 @@ class GroupMemberView2: UIView {
|
|||
.topToBottomOfView(stayTxtLab, offset: 14)
|
||||
.leftToView(stayTxtLab)
|
||||
|
||||
view.addSubview(todayTrackVipView)
|
||||
todayTrackVipView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
todayTrackVipView.topAnchor.constraint(equalTo: infoView.topAnchor),
|
||||
todayTrackVipView.leadingAnchor.constraint(equalTo: infoView.leadingAnchor),
|
||||
todayTrackVipView.trailingAnchor.constraint(equalTo: infoView.trailingAnchor),
|
||||
todayTrackVipView.bottomAnchor.constraint(equalTo: infoView.bottomAnchor)
|
||||
])
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var todayTrackVipView: UIImageView = {
|
||||
let view = UIImageView(image: UIImage(named: "MemberVipRequired/today_track"))
|
||||
view.contentMode = .scaleAspectFill
|
||||
view.clipsToBounds = true
|
||||
view.cornerRadius = 16
|
||||
view.isHidden = true
|
||||
view.isUserInteractionEnabled = false
|
||||
return view
|
||||
}()
|
||||
|
||||
|
|
@ -1630,20 +1551,13 @@ private final class CoupleMemberSlot: UIControl {
|
|||
avatarImageView.setHeadPic(model?.displayHeadPic)
|
||||
avatarImageView.layer.borderColor = UIColor(hexStr: isSelected ? "#FFD43B" : "#FFFFFF").cgColor
|
||||
nameLabel.text = model?.showName
|
||||
refreshVipVisibility()
|
||||
}
|
||||
|
||||
func refreshVipVisibility() {
|
||||
vipImageView.isHidden = member?.shouldShowVipAvatarFrame != true
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
addSubview(avatarImageView)
|
||||
addSubview(vipImageView)
|
||||
addSubview(nameLabel)
|
||||
|
||||
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
vipImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
|
|
@ -1652,9 +1566,6 @@ private final class CoupleMemberSlot: UIControl {
|
|||
avatarImageView.widthAnchor.constraint(equalToConstant: 50),
|
||||
avatarImageView.heightAnchor.constraint(equalToConstant: 50),
|
||||
|
||||
vipImageView.topAnchor.constraint(equalTo: avatarImageView.topAnchor, constant: -2),
|
||||
vipImageView.centerXAnchor.constraint(equalTo: avatarImageView.centerXAnchor),
|
||||
|
||||
nameLabel.topAnchor.constraint(equalTo: avatarImageView.bottomAnchor, constant: 9),
|
||||
nameLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
nameLabel.trailingAnchor.constraint(equalTo: trailingAnchor)
|
||||
|
|
@ -1671,13 +1582,6 @@ private final class CoupleMemberSlot: UIControl {
|
|||
return view
|
||||
}()
|
||||
|
||||
private let vipImageView: UIImageView = {
|
||||
let view = UIImageView(image: UIImage(named: "GroupMemberList/vip"))
|
||||
view.contentMode = .scaleAspectFit
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
private let nameLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 12, weight: .medium)
|
||||
|
|
|
|||
|
|
@ -1,913 +0,0 @@
|
|||
//
|
||||
// HomeView.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/5/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
import Lottie
|
||||
#if !targetEnvironment(simulator)
|
||||
import AMapNaviKit
|
||||
import MarqueeLabel
|
||||
#endif
|
||||
|
||||
class HomeView: UIView {
|
||||
|
||||
var disposeBag = DisposeBag()
|
||||
|
||||
let groupMemberView = GroupMemberView(frame: .zero)
|
||||
|
||||
let quickMessageView = QuickMessageView(frame: CGRectMake(0, kScreenHeight, kScreenWidth, 93))
|
||||
|
||||
let interactionView = InteractionView(frame: CGRectMake(0, kScreenHeight, kScreenWidth, 384))
|
||||
|
||||
var gpsSignalViewList: [UIView] = []
|
||||
|
||||
/// 根据信号格数更新信号条颜色
|
||||
func updateGPSSignal(bars: Int) {
|
||||
let filled = max(0, min(bars, gpsSignalViewList.count))
|
||||
for (i, view) in gpsSignalViewList.enumerated() {
|
||||
view.backgroundColor = i < filled ? UIColor(hexStr: "#4ED178") : UIColor(hexStr: "#E0E0E0")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - groupMemberView 拖拽
|
||||
private var groupMemberTopConstraint: NSLayoutConstraint?
|
||||
private var groupMemberUpLimit: CGFloat = 0
|
||||
private var groupMemberDownLimit: CGFloat = 0
|
||||
private var isGroupMemberLimitsSet = false
|
||||
private var panStartTop: CGFloat = 0
|
||||
/// 内嵌 tableView 是否可滑动(最大化时才可)
|
||||
private var isSubCanScroll = false
|
||||
|
||||
private var groupMemberTableTopOffset: CGFloat {
|
||||
-groupMemberView.tableView.adjustedContentInset.top
|
||||
}
|
||||
|
||||
// MARK: - Map
|
||||
#if !targetEnvironment(simulator)
|
||||
lazy var mapView: MAMapView! = {
|
||||
let mv = MAMapView()
|
||||
mv.zoomLevel = 16
|
||||
mv.showsUserLocation = true
|
||||
mv.userTrackingMode = .none
|
||||
mv.showsCompass = false
|
||||
mv.showsScale = false
|
||||
mv.isRotateEnabled = false
|
||||
mv.isRotateCameraEnabled = false
|
||||
DispatchQueue.main.async { mv.logoCenter = CGPoint(x: kScreenWidth - 50, y: kScreenHeight / 2 + 65) }
|
||||
return mv
|
||||
}()
|
||||
#else
|
||||
lazy var mapPlaceholderView: UIView = {
|
||||
let v = UIView()
|
||||
v.backgroundColor = UIColor(hexStr: "#EDEDED")
|
||||
let label = UILabel()
|
||||
label.text = "Map requires a real device"
|
||||
label.font = UIFont.systemFont(ofSize: 14)
|
||||
label.textColor = UIColor(hexStr: "#999999")
|
||||
label.textAlignment = .center
|
||||
v.addSubview(label)
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
label.centerXAnchor.constraint(equalTo: v.centerXAnchor),
|
||||
label.centerYAnchor.constraint(equalTo: v.centerYAnchor)
|
||||
])
|
||||
return v
|
||||
}()
|
||||
#endif
|
||||
|
||||
/// 面板关闭回调
|
||||
var onDismissPanel: (() -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
private func setupRx() {
|
||||
quickMessageView.closeBtn.rx.tap
|
||||
.subscribe(onNext: { [weak self] _ in
|
||||
self?.dismissMemberPanel()
|
||||
self?.onDismissPanel?()
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
groupMemberView.tableView.rx.setDelegate(self)
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
// 查找位置
|
||||
searchLottieView.rx.tapGesture.subscribe { _ in
|
||||
AppRouter.push(Route.searchLocation)
|
||||
}.disposed(by: disposeBag)
|
||||
|
||||
// 感叹号
|
||||
warningIconView.rx.tapGesture.subscribe { _ in
|
||||
LocationPermissionPopView.show()
|
||||
}.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
#if !targetEnvironment(simulator)
|
||||
addSubview(mapView)
|
||||
#else
|
||||
addSubview(mapPlaceholderView)
|
||||
#endif
|
||||
|
||||
addSubview(navBarBg)
|
||||
addSubview(avatarImgView)
|
||||
addSubview(groupView)
|
||||
groupView.addSubview(groupIconView)
|
||||
groupView.addSubview(groupNameLab)
|
||||
groupView.addSubview(groupArrowIconView)
|
||||
addSubview(messageView)
|
||||
addSubview(messageStackView)
|
||||
addSubview(toolsView)
|
||||
addSubview(warningIconView)
|
||||
addSubview(searchLottieView)
|
||||
addSubview(locationView)
|
||||
locationView.addSubview(locationIconView)
|
||||
addSubview(emojiPopView)
|
||||
addSubview(sosPopView)
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
mapView.layoutChain
|
||||
.top()
|
||||
.edges(excludingEdge: .top)
|
||||
sendSubviewToBack(mapView)
|
||||
#else
|
||||
mapPlaceholderView.layoutChain
|
||||
.topToBottomOfView(navBarBg)
|
||||
.left(0).right(0).bottom(0)
|
||||
#endif
|
||||
|
||||
navBarBg.layoutChain
|
||||
.edges(excludingEdge: .bottom)
|
||||
.heightToWidth(160/375)
|
||||
|
||||
avatarImgView.layoutChain
|
||||
.top(59)
|
||||
.left(15)
|
||||
.width(36)
|
||||
.heightToWidth(1)
|
||||
|
||||
groupView.layoutChain
|
||||
.centerY(avatarImgView)
|
||||
.height(36)
|
||||
.centerX()
|
||||
.width(185, relation: .greaterThanOrEqual)
|
||||
|
||||
groupIconView.layoutChain
|
||||
.left(11)
|
||||
.centerY()
|
||||
.width(30)
|
||||
.height(30)
|
||||
|
||||
groupArrowIconView.layoutChain
|
||||
.right(15)
|
||||
.centerY()
|
||||
.width(15)
|
||||
.height(8.5)
|
||||
|
||||
groupNameLab.layoutChain
|
||||
.edgesVertical()
|
||||
.leftToRightOfView(groupIconView)
|
||||
.rightToLeftOfView(groupArrowIconView)
|
||||
|
||||
messageView.layoutChain
|
||||
.right(15)
|
||||
.centerY(groupView)
|
||||
.width(36)
|
||||
.height(36)
|
||||
|
||||
messageStackView.layoutChain
|
||||
.topToBottomOfView(messageView, offset: 23)
|
||||
.edgesHorzontal(15)
|
||||
|
||||
toolsView.layoutChain
|
||||
.left(15)
|
||||
.width(40)
|
||||
.centerY(self, offset: -50)
|
||||
|
||||
warningIconView.layoutChain
|
||||
.bottomToTopOfView(toolsView, offset: -10)
|
||||
.centerX(toolsView)
|
||||
.width(28)
|
||||
.heightToWidth(1)
|
||||
|
||||
searchLottieView.layoutChain
|
||||
.centerY(toolsView)
|
||||
.right()
|
||||
.width(100)
|
||||
.height(100)
|
||||
|
||||
locationView.layoutChain
|
||||
.right(15)
|
||||
.bottomToView(toolsView, offset: -10)
|
||||
.width(40)
|
||||
.height(40)
|
||||
|
||||
locationIconView.layoutChain
|
||||
.centerX()
|
||||
.centerY()
|
||||
|
||||
emojiPopView.layoutChain
|
||||
.centerX().centerY()
|
||||
.width(180).height(180)
|
||||
|
||||
sosPopView.layoutChain
|
||||
.bottomToView(toolsView)
|
||||
.leftToRightOfView(toolsView)
|
||||
.height(57).width(57)
|
||||
|
||||
// 圈子成员View
|
||||
addSubview(groupMemberView)
|
||||
|
||||
groupMemberView.layoutChain
|
||||
.topToBottomOfView(toolsView, offset: 10)
|
||||
.edgesHorzontal()
|
||||
.bottom()
|
||||
|
||||
groupMemberTopConstraint = groupMemberView.jh_constraint(
|
||||
.top, toAttribute: .bottom, otherView: toolsView, relation: .equal
|
||||
)
|
||||
|
||||
let pan = UIPanGestureRecognizer(target: self, action: #selector(handleGroupMemberPan(_:)))
|
||||
pan.delegate = self
|
||||
groupMemberView.addGestureRecognizer(pan)
|
||||
groupMemberView.tableView.isScrollEnabled = false
|
||||
|
||||
// 底部弹出面板(quickMessageView 在上,interactionView 在下)
|
||||
addSubview(interactionView)
|
||||
addSubview(quickMessageView)
|
||||
}
|
||||
|
||||
// MARK: - 底部面板显隐
|
||||
func showMemberPanel(member: CircleMember) {
|
||||
interactionView.configure(member: member)
|
||||
quickMessageView.isHidden = false
|
||||
interactionView.isHidden = false
|
||||
toolsView.isHidden = true
|
||||
searchLottieView.isHidden = true
|
||||
quickMessageView.frame.origin.y = kScreenHeight - 384 - 93
|
||||
interactionView.frame.origin.y = kScreenHeight - 384
|
||||
}
|
||||
|
||||
func dismissMemberPanel() {
|
||||
quickMessageView.frame.origin.y = kScreenHeight
|
||||
interactionView.frame.origin.y = kScreenHeight
|
||||
quickMessageView.isHidden = true
|
||||
interactionView.isHidden = true
|
||||
toolsView.isHidden = false
|
||||
searchLottieView.isHidden = AppContextManager.shared.systemConfig?.isIntercept == true
|
||||
onDismissPanel?()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: .zero)
|
||||
backgroundColor = .white
|
||||
setupUI()
|
||||
setupRx()
|
||||
|
||||
searchLottieView.play()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: - Layout
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
noticeView.layoutIfNeeded()
|
||||
noticeInfoView.setGradientLayer(frame: noticeInfoView.bounds,
|
||||
startPoint: CGPoint(x: 0, y: 0.5),
|
||||
endPoint: CGPoint(x: 1, y: 0.5),
|
||||
colors: [UIColor(hexStr: "#FFFFFF", alpha: 0), UIColor(hexStr: "#FFFFFF"), UIColor(hexStr: "#FFFFFF", alpha: 0)],
|
||||
locations: [0, 0.5, 1])
|
||||
|
||||
if !isGroupMemberLimitsSet {
|
||||
isGroupMemberLimitsSet = true
|
||||
groupMemberDownLimit = groupMemberView.frame.minY
|
||||
groupMemberUpLimit = groupView.frame.maxY + 20
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Pan Gesture
|
||||
@objc private func handleGroupMemberPan(_ pan: UIPanGestureRecognizer) {
|
||||
guard isGroupMemberLimitsSet, let topConstraint = groupMemberTopConstraint else { return }
|
||||
|
||||
switch pan.state {
|
||||
case .began:
|
||||
layoutIfNeeded()
|
||||
panStartTop = groupMemberView.frame.minY
|
||||
|
||||
case .changed:
|
||||
let newTop = panStartTop + pan.translation(in: self).y
|
||||
let scrollOffset = groupMemberView.tableView.contentOffset.y
|
||||
|
||||
// 最大化状态下,tableView 可以向下滑时,不干扰自身滑动;
|
||||
// 只有 tableView 到达顶部继续下拉时,才切换为 view 拖拽
|
||||
if isSubCanScroll {
|
||||
if scrollOffset > groupMemberTableTopOffset + 0.5 {
|
||||
// 内容正在滑动,不移动 GroupMemberView
|
||||
return
|
||||
}
|
||||
|
||||
if pan.velocity(in: self).y > 0 || groupMemberView.frame.minY > groupMemberUpLimit + 1 {
|
||||
// 内容真正到顶并继续下拉 → 切回面板拖拽
|
||||
isSubCanScroll = false
|
||||
panStartTop = groupMemberView.frame.minY
|
||||
}
|
||||
} else {
|
||||
// GroupMemberView 在顶部且继续上滑 → 激活内容滑动
|
||||
if groupMemberView.frame.minY <= groupMemberUpLimit && newTop <= groupMemberUpLimit {
|
||||
isSubCanScroll = true
|
||||
panStartTop = groupMemberView.frame.minY
|
||||
topConstraint.constant = groupMemberUpLimit - groupMemberDownLimit + 10
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let clamped = max(groupMemberUpLimit, min(groupMemberDownLimit, newTop))
|
||||
topConstraint.constant = clamped - groupMemberDownLimit + 10
|
||||
|
||||
case .ended, .cancelled:
|
||||
let velocity = pan.velocity(in: self)
|
||||
let isNearUp = abs(groupMemberView.frame.minY - groupMemberUpLimit) < abs(groupMemberView.frame.minY - groupMemberDownLimit)
|
||||
let target: CGFloat
|
||||
if groupMemberView.frame.minY <= groupMemberUpLimit + 5 {
|
||||
// 顶部附近由位置决定去向,避免 tableView 松手速度触发父面板误回收
|
||||
target = isNearUp ? groupMemberUpLimit : groupMemberDownLimit
|
||||
} else if abs(velocity.y) > 200 {
|
||||
target = velocity.y < 0 ? groupMemberUpLimit : groupMemberDownLimit
|
||||
} else {
|
||||
target = isNearUp ? groupMemberUpLimit : groupMemberDownLimit
|
||||
}
|
||||
topConstraint.constant = target - groupMemberDownLimit + 10
|
||||
let atTop = target == self.groupMemberUpLimit
|
||||
if !atTop {
|
||||
isSubCanScroll = false
|
||||
groupMemberView.tableView.isScrollEnabled = false
|
||||
groupMemberView.tableView.setContentOffset(CGPoint(x: 0, y: groupMemberTableTopOffset), animated: false)
|
||||
}
|
||||
isSubCanScroll = atTop
|
||||
|
||||
UIView.animate(withDuration: 0.25, delay: 0,
|
||||
options: [.curveEaseOut, .allowUserInteraction]) {
|
||||
self.layoutIfNeeded()
|
||||
} completion: { _ in
|
||||
self.groupMemberView.tableView.isScrollEnabled = atTop
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/// 收起 GroupMemberView(选中成员后调用)
|
||||
func dismissGroupMemberView() {
|
||||
guard isGroupMemberLimitsSet, let topConstraint = groupMemberTopConstraint else { return }
|
||||
isSubCanScroll = false
|
||||
groupMemberView.tableView.isScrollEnabled = false
|
||||
topConstraint.constant = groupMemberDownLimit - groupMemberDownLimit + 10
|
||||
groupMemberView.tableView.setContentOffset(CGPoint(x: 0, y: groupMemberTableTopOffset), animated: false)
|
||||
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut) {
|
||||
self.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UI Components
|
||||
lazy var navBarBg: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Home/navBar_bg")
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var avatarImgView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.backgroundColor = .lightGray
|
||||
view.contentMode = .scaleAspectFill
|
||||
view.cornerRadius = 18
|
||||
return view
|
||||
}()
|
||||
|
||||
// 圈子选择
|
||||
lazy var groupView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .white
|
||||
view.cornerRadius = 18
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var groupIconView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Home/group")
|
||||
view.backgroundColor = .clear
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var groupNameLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
label.textColor = UIColor(hexStr: "#0F2846")
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var groupArrowIconView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Home/arrow_down")
|
||||
view.backgroundColor = .clear
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
/// 消息
|
||||
lazy var messageView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .white
|
||||
view.cornerRadius = 18
|
||||
view.clipsToBounds = false
|
||||
view.isHidden = true
|
||||
|
||||
view.addSubview(messageIcon)
|
||||
view.addSubview(messageDotView)
|
||||
|
||||
messageIcon.layoutChain
|
||||
.centerX().centerY()
|
||||
|
||||
messageDotView.layoutChain
|
||||
.top()
|
||||
.right()
|
||||
.width(10)
|
||||
.height(10)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var messageIcon: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Home/message")
|
||||
view.backgroundColor = .clear
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var messageDotView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = UIColor(hexStr: "#FD5E61")
|
||||
view.cornerRadius = 5
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var messageStackView: UIStackView = {
|
||||
let view = UIStackView(arrangedSubviews: [tipsView, messageBubbleView])
|
||||
view.axis = .vertical
|
||||
view.alignment = .leading
|
||||
view.spacing = 5
|
||||
view.backgroundColor = .clear
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var tipsView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
view.layoutChain.height(22)
|
||||
|
||||
view.addSubview(gpsSignalView)
|
||||
gpsSignalView.layoutChain
|
||||
.left()
|
||||
.centerY()
|
||||
|
||||
view.addSubview(noticeView)
|
||||
noticeView.layoutChain
|
||||
.leftToRightOfView(gpsSignalView, offset: 0)
|
||||
.right(15)
|
||||
.edgesVertical()
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
// MARK: - 卫星信号
|
||||
lazy var gpsSignalView: UIView = {
|
||||
let view = UIView()
|
||||
view.cornerRadius = 5
|
||||
view.backgroundColor = .black.withAlphaComponent(0.4)
|
||||
|
||||
let signalView1 = UIView()
|
||||
signalView1.backgroundColor = UIColor(hexStr: "#E6E6E6")
|
||||
view.addSubview(signalView1)
|
||||
signalView1.layoutChain
|
||||
.left(7)
|
||||
.bottom(5)
|
||||
.width(3)
|
||||
.height(4)
|
||||
|
||||
let signalView2 = UIView()
|
||||
signalView2.backgroundColor = UIColor(hexStr: "#E6E6E6")
|
||||
view.addSubview(signalView2)
|
||||
signalView2.layoutChain
|
||||
.leftToRightOfView(signalView1, offset: 2)
|
||||
.bottomToView(signalView1)
|
||||
.width(3)
|
||||
.height(6)
|
||||
|
||||
let signalView3 = UIView()
|
||||
signalView3.backgroundColor = UIColor(hexStr: "#E6E6E6")
|
||||
view.addSubview(signalView3)
|
||||
signalView3.layoutChain
|
||||
.leftToRightOfView(signalView2, offset: 2)
|
||||
.bottomToView(signalView1)
|
||||
.width(3)
|
||||
.height(8)
|
||||
|
||||
let signalView4 = UIView()
|
||||
signalView4.backgroundColor = UIColor(hexStr: "#E6E6E6")
|
||||
view.addSubview(signalView4)
|
||||
signalView4.layoutChain
|
||||
.leftToRightOfView(signalView3, offset: 2)
|
||||
.bottomToView(signalView1)
|
||||
.width(3)
|
||||
.height(10)
|
||||
|
||||
let label = UILabel()
|
||||
label.text = "卫星信号"
|
||||
label.font = .systemFont(ofSize: 10, weight: .medium)
|
||||
label.textColor = .white
|
||||
view.addSubview(label)
|
||||
label.layoutChain
|
||||
.leftToRightOfView(signalView4, offset: 2)
|
||||
.edgesVertical(4)
|
||||
.right(7)
|
||||
|
||||
gpsSignalViewList = [signalView1, signalView2, signalView3, signalView4]
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
// MARK: - 公告
|
||||
lazy var noticeView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
view.addSubview(noticeInfoView)
|
||||
noticeInfoView.layoutChain
|
||||
.left()
|
||||
.right()
|
||||
.edgesVertical()
|
||||
|
||||
view.addSubview(noticeCloseBtn)
|
||||
noticeCloseBtn.layoutChain
|
||||
.right()
|
||||
.width(16)
|
||||
.height(16)
|
||||
.centerY()
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var noticeInfoView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
// let titleLab = UILabel()
|
||||
// titleLab.text = "公告信息"
|
||||
// titleLab.font = .systemFont(ofSize: 10, weight: .medium)
|
||||
// titleLab.textColor = UIColor(hexStr: "#0F2948")
|
||||
// view.addSubview(titleLab)
|
||||
// titleLab.layoutChain
|
||||
// .left(16)
|
||||
// .centerY()
|
||||
// .width(40)
|
||||
|
||||
view.addSubview(noticeLab)
|
||||
noticeLab.layoutChain
|
||||
.left(16)
|
||||
.centerY()
|
||||
.right(24)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var noticeLab: CallbackMarqueeLabel = {
|
||||
let label = CallbackMarqueeLabel()
|
||||
label.font = .systemFont(ofSize: 10, weight: .medium)
|
||||
label.textColor = UIColor(hexStr: "#0F2948")
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var noticeCloseBtn: UIButton = {
|
||||
let btn = UIButton()
|
||||
btn.setImage(UIImage(named: "Common/x_black"), for: .normal)
|
||||
btn.backgroundColor = .white
|
||||
btn.cornerRadius = 8
|
||||
btn.extendEdgeInsets = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)
|
||||
btn.rx.tap.subscribe(onNext: { [weak self] _ in
|
||||
self?.noticeView.isHidden = true
|
||||
}).disposed(by: disposeBag)
|
||||
return btn
|
||||
}()
|
||||
|
||||
// MARK: - 消息气泡
|
||||
lazy var messageBubbleView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .white.withAlphaComponent(0.8)
|
||||
view.cornerRadius = 10
|
||||
view.isHidden = true
|
||||
|
||||
view.addSubview(messageLab)
|
||||
messageLab.layoutChain
|
||||
.edges(all: 12)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var messageLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 12, weight: .medium)
|
||||
label.textColor = UIColor(hexStr: "#0F2846")
|
||||
return label
|
||||
}()
|
||||
|
||||
// MARK: - SOS
|
||||
lazy var sosPopView: LottieAnimationView = {
|
||||
let view = LottieAnimationView(name: "siren")
|
||||
view.loopMode = .loop
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
// MARK: - 表情展示
|
||||
lazy var emojiPopView: LottieAnimationView = {
|
||||
let view = LottieAnimationView()
|
||||
view.loopMode = .playOnce
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
// MARK: - warning 惊叹号图标
|
||||
lazy var warningIconView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Home/warning")
|
||||
view.contentMode = .scaleAspectFill
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
// MARK: - 侧边工具栏
|
||||
lazy var toolsView: UIStackView = {
|
||||
let view = UIStackView(arrangedSubviews: [bubbleView, signInView, sosView, scheduleView])
|
||||
view.axis = .vertical
|
||||
view.distribution = .fill
|
||||
view.alignment = .fill
|
||||
view.backgroundColor = .black.withAlphaComponent(0.5)
|
||||
view.cornerRadius = 20
|
||||
return view
|
||||
}()
|
||||
|
||||
// 气泡
|
||||
lazy var bubbleView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
view.addSubview(bubbleIcon)
|
||||
bubbleIcon.layoutChain
|
||||
.top(10)
|
||||
.centerX()
|
||||
.width(28)
|
||||
.height(28)
|
||||
|
||||
view.addSubview(bubbleLab)
|
||||
bubbleLab.layoutChain
|
||||
.topToBottomOfView(bubbleIcon, offset: 4)
|
||||
.edgesHorzontal()
|
||||
|
||||
let lineView = UIView()
|
||||
lineView.backgroundColor = .white
|
||||
view.addSubview(lineView)
|
||||
lineView.layoutChain
|
||||
.width(12)
|
||||
.height(2)
|
||||
.centerX()
|
||||
.bottom(7)
|
||||
lineView.layoutChain.topToBottomOfView(bubbleLab, offset: 4)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var bubbleIcon: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Home/bubble")
|
||||
view.backgroundColor = .clear
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var bubbleLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = "气泡"
|
||||
label.font = .systemFont(ofSize: 10, weight: .medium)
|
||||
label.textColor = .white
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
// 签到
|
||||
lazy var signInView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
view.addSubview(signInIcon)
|
||||
signInIcon.layoutChain
|
||||
.top()
|
||||
.centerX()
|
||||
.width(28)
|
||||
.height(28)
|
||||
|
||||
view.addSubview(signInLab)
|
||||
signInLab.layoutChain
|
||||
.topToBottomOfView(signInIcon, offset: 4)
|
||||
.edgesHorzontal()
|
||||
|
||||
let lineView = UIView()
|
||||
lineView.backgroundColor = .white
|
||||
view.addSubview(lineView)
|
||||
lineView.layoutChain
|
||||
.width(12)
|
||||
.height(2)
|
||||
.centerX()
|
||||
.bottom(7)
|
||||
lineView.layoutChain.topToBottomOfView(signInLab, offset: 4)
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var signInIcon: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Home/signIn")
|
||||
view.backgroundColor = .clear
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var signInLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = "签到"
|
||||
label.font = .systemFont(ofSize: 10, weight: .medium)
|
||||
label.textColor = .white
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
// SOS
|
||||
lazy var sosView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
view.addSubview(sosIcon)
|
||||
view.addSubview(sosLab)
|
||||
|
||||
sosIcon.layoutChain
|
||||
.top()
|
||||
.centerX()
|
||||
.width(28)
|
||||
.height(28)
|
||||
|
||||
sosLab.layoutChain
|
||||
.topToBottomOfView(sosIcon, offset: 4)
|
||||
.edgesHorzontal()
|
||||
|
||||
let lineView = UIView()
|
||||
lineView.backgroundColor = .white
|
||||
view.addSubview(lineView)
|
||||
lineView.layoutChain
|
||||
.width(12)
|
||||
.height(2)
|
||||
.centerX()
|
||||
.bottom(7)
|
||||
lineView.layoutChain.topToBottomOfView(sosLab, offset: 4)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var sosIcon: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Home/sos")
|
||||
view.backgroundColor = .clear
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var sosLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = "SOS"
|
||||
label.font = .systemFont(ofSize: 10, weight: .medium)
|
||||
label.textColor = .white
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
// 行程
|
||||
lazy var scheduleView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
let icon = UIImageView(image: UIImage(named: "Home/schedule"))
|
||||
icon.contentMode = .scaleAspectFill
|
||||
view.addSubview(icon)
|
||||
icon.layoutChain
|
||||
.top()
|
||||
.centerX()
|
||||
.width(28)
|
||||
.height(28)
|
||||
|
||||
let label = UILabel()
|
||||
label.text = "行程"
|
||||
label.font = .systemFont(ofSize: 10, weight: .medium)
|
||||
label.textColor = .white
|
||||
label.textAlignment = .center
|
||||
view.addSubview(label)
|
||||
label.layoutChain
|
||||
.topToBottomOfView(icon, offset: 4)
|
||||
.edgesHorzontal()
|
||||
label.layoutChain.bottom(10)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
// MARK: - 查位置
|
||||
lazy var searchLottieView: LottieAnimationView = {
|
||||
let view = LottieAnimationView(name: "home_search")
|
||||
view.loopMode = .loop
|
||||
return view
|
||||
}()
|
||||
|
||||
// MARK: - 定位按钮
|
||||
lazy var locationView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .black.withAlphaComponent(0.4)
|
||||
view.cornerRadius = 20
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var locationIconView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.backgroundColor = .clear
|
||||
view.image = UIImage(named: "Home/location")
|
||||
return view
|
||||
}()
|
||||
}
|
||||
|
||||
// MARK: - UIScrollViewDelegate
|
||||
extension HomeView: UIScrollViewDelegate {
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
guard scrollView == groupMemberView.tableView else { return }
|
||||
if scrollView.contentOffset.y > groupMemberTableTopOffset {
|
||||
isSubCanScroll = true
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
guard scrollView == groupMemberView.tableView else { return }
|
||||
if isSubCanScroll {
|
||||
if scrollView.contentOffset.y <= groupMemberTableTopOffset {
|
||||
isSubCanScroll = false
|
||||
scrollView.contentOffset = CGPoint(x: 0, y: groupMemberTableTopOffset)
|
||||
}
|
||||
} else {
|
||||
scrollView.contentOffset = CGPoint(x: 0, y: groupMemberTableTopOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIGestureRecognizerDelegate
|
||||
extension HomeView: UIGestureRecognizerDelegate {
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer) -> Bool {
|
||||
// 允许 GroupMemberView 的 pan 与 tableView 的 pan 同时识别
|
||||
// 滚动状态由 isSubCanScroll + contentOffset 控制,不影响手势判定
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CallbackMarqueeLabel
|
||||
class CallbackMarqueeLabel: MarqueeLabel {
|
||||
// 闭包回调对外抛出事件
|
||||
var onScrollLoopComplete: (() -> Void)?
|
||||
var onScrollStart: (() -> Void)?
|
||||
|
||||
// 滚动开始
|
||||
override func labelWillBeginScroll() {
|
||||
super.labelWillBeginScroll()
|
||||
onScrollStart?()
|
||||
}
|
||||
|
||||
// 单次滚动一圈完成
|
||||
override func labelReturnedToHome(_ finished: Bool) {
|
||||
super.labelReturnedToHome(finished)
|
||||
// 只处理正常滚动结束,过滤中途重置
|
||||
guard finished else { return }
|
||||
onScrollLoopComplete?()
|
||||
}
|
||||
}
|
||||
|
|
@ -72,7 +72,6 @@ class HomeViewController: BaseViewController {
|
|||
private var lastUpdateTimes: [String: Date] = [:]
|
||||
/// 离线检测定时器
|
||||
private var offlineCheckTimer: Timer?
|
||||
private var popupQueue: PopupQueueManager?
|
||||
private static var sosPlayerKey: UInt8 = 0
|
||||
private var groupRefreshWorkItem: DispatchWorkItem?
|
||||
private var groupDataRetryWorkItem: DispatchWorkItem?
|
||||
|
|
@ -330,12 +329,7 @@ class HomeViewController: BaseViewController {
|
|||
|
||||
// 今日轨迹二级页
|
||||
rootView.groupMemberView.todayTrackView.rx.tapGesture.subscribe(onNext: { [weak self] _ in
|
||||
guard let self else { return }
|
||||
if self.rootView.groupMemberView.isTodayTrackVipRequired {
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
} else {
|
||||
self.openTodayTrackDetail()
|
||||
}
|
||||
self?.openTodayTrackDetail()
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
// 实时互动
|
||||
|
|
@ -350,10 +344,6 @@ class HomeViewController: BaseViewController {
|
|||
// 手机报告二级页
|
||||
rootView.groupMemberView.phoneReportView.rx.tapGesture.subscribe(onNext: { [weak self] _ in
|
||||
guard let self else { return }
|
||||
if self.rootView.groupMemberView.isPhoneReportVipRequired {
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
return
|
||||
}
|
||||
guard !self.rootView.groupMemberView.isPhoneReportDenied else { return }
|
||||
self.openPhoneReportDetail()
|
||||
}).disposed(by: disposeBag)
|
||||
|
|
@ -361,10 +351,6 @@ class HomeViewController: BaseViewController {
|
|||
// APP 报告二级页
|
||||
rootView.groupMemberView.appUsageView.rx.tapGesture.subscribe(onNext: { [weak self] _ in
|
||||
guard let self else { return }
|
||||
if self.rootView.groupMemberView.isAppUsageVipRequired {
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
return
|
||||
}
|
||||
guard !self.rootView.groupMemberView.isAppUsageDenied else { return }
|
||||
self.openAppUsageReport()
|
||||
}).disposed(by: disposeBag)
|
||||
|
|
@ -384,9 +370,6 @@ class HomeViewController: BaseViewController {
|
|||
// User Config刷新
|
||||
NotificationCenter.default.rx.notification(.RefreshUserConfigNotification, object: nil)
|
||||
.startWith(Notification(name: .RefreshUserConfigNotification))
|
||||
.do(onNext: { [weak self] _ in
|
||||
self?.popupQueue = nil
|
||||
})
|
||||
.flatMapLatest { _ in
|
||||
SystemService.userConfig()
|
||||
}
|
||||
|
|
@ -395,15 +378,11 @@ class HomeViewController: BaseViewController {
|
|||
Defaults[\.loginToken] = model.token
|
||||
AppContextManager.shared.systemConfig = model.config
|
||||
self.rootView.groupMemberView.refreshInterceptState()
|
||||
// rootView.searchLottieView.isHidden = model.config?.isIntercept == true
|
||||
self.requestUserInfo { [weak self] in
|
||||
self?.requestGroupInfo()
|
||||
}
|
||||
self.requestNotice()
|
||||
self.refreshPigeonInbox()
|
||||
self.popupQueue = nil
|
||||
self.popupQueue = PopupQueueManager()
|
||||
// self.popupQueue?.start(from: self)
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
// 圈子刷新
|
||||
|
|
@ -849,7 +828,7 @@ class HomeViewController: BaseViewController {
|
|||
return
|
||||
}
|
||||
|
||||
self.viewModel.updateGroupUsers(response.list, vip: response.vip)
|
||||
self.viewModel.updateGroupUsers(response.list)
|
||||
// 成员接口是气泡结束的权威来源:status=0 时重新显示地图点。
|
||||
self.syncMemberAnnotations(self.viewModel.memberList)
|
||||
self.refreshMQTTSubscriptions(self.viewModel.memberList)
|
||||
|
|
@ -1184,8 +1163,7 @@ class HomeViewController: BaseViewController {
|
|||
rootView.groupMemberView.setupMemberInfo(
|
||||
model,
|
||||
isOwner: viewModel.isGroupOwn(id: model.user_id),
|
||||
phoneUsage: usage,
|
||||
groupVip: viewModel.groupVip
|
||||
phoneUsage: usage
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@ class HomeViewModel {
|
|||
}
|
||||
|
||||
private(set) var memberList: [GroupMemberModel] = []
|
||||
private(set) var groupVip = GroupMemberVip()
|
||||
|
||||
func markMemberInBubble(userId: String) {
|
||||
updateMemberStatus(userId: userId, status: 2)
|
||||
|
|
@ -113,13 +112,11 @@ class HomeViewModel {
|
|||
sectionedItems.onNext(memberList.mapSection())
|
||||
}
|
||||
|
||||
func updateGroupUsers(_ list: [GroupMemberModel], vip: GroupMemberVip) {
|
||||
groupVip = vip
|
||||
func updateGroupUsers(_ list: [GroupMemberModel]) {
|
||||
updateMembers(list)
|
||||
}
|
||||
|
||||
func clearMembers() {
|
||||
groupVip = GroupMemberVip()
|
||||
updateMembers([])
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,8 +76,5 @@ final class InteractionEmojiCell: UICollectionViewCell {
|
|||
let path = Bundle.main.path(forResource: "\(pngName)", ofType: "png") {
|
||||
imageView.image = UIImage(contentsOfFile: path)
|
||||
}
|
||||
|
||||
guard AppContextManager.shared.systemConfig?.isIntercept == false else { return }
|
||||
lockView.isHidden = name.hasPrefix("normal_") || AppContextManager.shared.vip > 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,22 +25,15 @@ class InteractionView: UIView {
|
|||
nameLab.text = member.name
|
||||
locationLab.text = member.address
|
||||
|
||||
// 会员权益
|
||||
if AppContextManager.shared.vip > 1 {
|
||||
batteryInfoView.isHidden = member.battery.int == 0
|
||||
// 电量
|
||||
let batteryInt = Int(member.battery) ?? 0
|
||||
let batteryPercent = min(CGFloat(batteryInt), 100)
|
||||
batteryView.layoutChain.width(CGFloat(16 - 1) * batteryPercent / 100.0)
|
||||
batteryLab.text = "\(member.battery)%"
|
||||
batteryInfoView.isHidden = member.battery.int == 0
|
||||
// 电量
|
||||
let batteryInt = Int(member.battery) ?? 0
|
||||
let batteryPercent = min(CGFloat(batteryInt), 100)
|
||||
batteryView.layoutChain.width(CGFloat(16 - 1) * batteryPercent / 100.0)
|
||||
batteryLab.text = "\(member.battery)%"
|
||||
|
||||
// 趣味表情
|
||||
lockView.isHidden = true
|
||||
}
|
||||
else {
|
||||
batteryInfoView.isHidden = true
|
||||
lockView.isHidden = false
|
||||
}
|
||||
// 趣味表情
|
||||
lockView.isHidden = true
|
||||
}
|
||||
|
||||
/// 当前表情列表(normal / fun)
|
||||
|
|
@ -84,7 +77,7 @@ class InteractionView: UIView {
|
|||
emojiCollectionView.rx.modelSelected(String.self)
|
||||
.subscribe(onNext: { [weak self] name in
|
||||
guard let self = self else { return }
|
||||
if name.hasPrefix("fun_"), AppContextManager.shared.vip <= 1 { return }
|
||||
if name.hasPrefix("fun_") { return }
|
||||
// 解析 index: normal_5 → 15, fun_2 → 22
|
||||
let prefix: String
|
||||
let numStr: String
|
||||
|
|
@ -612,7 +605,7 @@ final class EmojiPanelCell: UICollectionViewCell {
|
|||
self.lottieView.play()
|
||||
}
|
||||
|
||||
lockView.isHidden = true//AppContextManager.shared.vip > 1
|
||||
lockView.isHidden = true
|
||||
}
|
||||
|
||||
func playAnimation() {
|
||||
|
|
|
|||
|
|
@ -74,10 +74,8 @@ final class MemberAppUsageView: UIView {
|
|||
private let usageTitleLab = UILabel()
|
||||
private let topTitleLab = UILabel()
|
||||
private let topStack = UIStackView()
|
||||
private let vipRequiredView = UIImageView()
|
||||
private let deniedOverlay = PrivilegeDeniedOverlay()
|
||||
private(set) var isDenied = false
|
||||
private(set) var isVipRequired = false
|
||||
var onDeniedAction: (() -> Void)?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
|
|
@ -92,7 +90,6 @@ final class MemberAppUsageView: UIView {
|
|||
|
||||
func setAccessState(_ state: MemberFeatureAccessState) {
|
||||
isDenied = state == .privilegeDenied
|
||||
isVipRequired = state == .vipRequired
|
||||
deniedOverlay.configure(
|
||||
title: "TA未开启设置",
|
||||
message: "无法查看app使用记录",
|
||||
|
|
@ -102,9 +99,8 @@ final class MemberAppUsageView: UIView {
|
|||
deniedOverlay.onAction = { [weak self] in self?.onDeniedAction?() }
|
||||
deniedOverlay.setVisible(isDenied)
|
||||
deniedOverlay.layer.cornerRadius = cardView.layer.cornerRadius
|
||||
vipRequiredView.isHidden = !isVipRequired
|
||||
[usageTitleLab, topTitleLab, donutView, totalTimeLab, totalHintLab, topStack].forEach {
|
||||
$0.isHidden = isVipRequired
|
||||
$0.isHidden = false
|
||||
}
|
||||
gestureRecognizers?.forEach { $0.isEnabled = !isDenied }
|
||||
}
|
||||
|
|
@ -207,14 +203,6 @@ final class MemberAppUsageView: UIView {
|
|||
.left(160)
|
||||
.right(14)
|
||||
|
||||
vipRequiredView.image = UIImage(named: "MemberVipRequired/app_usage")
|
||||
vipRequiredView.contentMode = .scaleAspectFill
|
||||
vipRequiredView.clipsToBounds = true
|
||||
vipRequiredView.isHidden = true
|
||||
vipRequiredView.isUserInteractionEnabled = false
|
||||
cardView.addSubview(vipRequiredView)
|
||||
vipRequiredView.layoutChain.edges()
|
||||
|
||||
cardView.bringSubviewToFront(deniedOverlay)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,10 +52,8 @@ final class MemberPhoneReportView: UIView {
|
|||
private let unlockIcon = UIImageView()
|
||||
private let unlockTitleLab = UILabel()
|
||||
private let unlockValueLab = UILabel()
|
||||
private let vipRequiredView = UIImageView()
|
||||
private let deniedOverlay = PrivilegeDeniedOverlay()
|
||||
private(set) var isDenied = false
|
||||
private(set) var isVipRequired = false
|
||||
var onDeniedAction: (() -> Void)?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
|
|
@ -70,7 +68,6 @@ final class MemberPhoneReportView: UIView {
|
|||
|
||||
func setAccessState(_ state: MemberFeatureAccessState) {
|
||||
isDenied = state == .privilegeDenied
|
||||
isVipRequired = state == .vipRequired
|
||||
deniedOverlay.configure(
|
||||
title: "TA未开启设置",
|
||||
message: "无法查看手机报告",
|
||||
|
|
@ -80,9 +77,8 @@ final class MemberPhoneReportView: UIView {
|
|||
deniedOverlay.onAction = { [weak self] in self?.onDeniedAction?() }
|
||||
deniedOverlay.setVisible(isDenied)
|
||||
deniedOverlay.layer.cornerRadius = cardView.layer.cornerRadius
|
||||
vipRequiredView.isHidden = !isVipRequired
|
||||
leftPanel.isHidden = isVipRequired
|
||||
rightPanel.isHidden = isVipRequired
|
||||
leftPanel.isHidden = false
|
||||
rightPanel.isHidden = false
|
||||
gestureRecognizers?.forEach { $0.isEnabled = !isDenied }
|
||||
}
|
||||
|
||||
|
|
@ -265,14 +261,6 @@ final class MemberPhoneReportView: UIView {
|
|||
.leftToRightOfView(unlockValueLab, offset: 3)
|
||||
.bottomToView(unlockValueLab, offset: -4)
|
||||
|
||||
vipRequiredView.image = UIImage(named: "MemberVipRequired/phone_report")
|
||||
vipRequiredView.contentMode = .scaleAspectFill
|
||||
vipRequiredView.clipsToBounds = true
|
||||
vipRequiredView.isHidden = true
|
||||
vipRequiredView.isUserInteractionEnabled = false
|
||||
cardView.addSubview(vipRequiredView)
|
||||
vipRequiredView.layoutChain.edges()
|
||||
|
||||
cardView.bringSubviewToFront(deniedOverlay)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,32 +36,13 @@ class SearchLocationResultVC: BaseViewController {
|
|||
applyResultPresentation()
|
||||
}
|
||||
|
||||
private func isVipUnlocked() -> Bool {
|
||||
AppContextManager.shared.vip > 1
|
||||
}
|
||||
|
||||
private func applyResultPresentation() {
|
||||
handleCode()
|
||||
let isVip = isVipUnlocked()
|
||||
rootView.updateVipAccess(isUnlocked: isVip)
|
||||
if !isVip {
|
||||
rootView.actionBtn.setTitle("开通会员 解锁功能", for: .normal)
|
||||
rootView.actionBtn.isHidden = false
|
||||
}
|
||||
rootView.updateSubViews()
|
||||
}
|
||||
|
||||
private func reactiveAction() {
|
||||
rootView.vipMaskView.rx.tap
|
||||
.subscribe(onNext: {
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
rootView.actionBtn.rx.tap.subscribe(onNext: { _ in
|
||||
guard self.isVipUnlocked() else {
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
return
|
||||
}
|
||||
switch self.code {
|
||||
case 0:
|
||||
NotificationCenter.default.post(name: .ShowMemberLocationNotification,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import Lottie
|
|||
class SearchLocationResultView: UIView {
|
||||
|
||||
let headerView = SearchLocationHeaderView()
|
||||
private var vipResultHeightConstraint: NSLayoutConstraint?
|
||||
|
||||
private func setupUI() {
|
||||
addSubview(navBgView)
|
||||
|
|
@ -45,15 +44,8 @@ class SearchLocationResultView: UIView {
|
|||
.height(showInviteRow ? 72 : 0)
|
||||
}
|
||||
|
||||
func updateVipAccess(isUnlocked: Bool) {
|
||||
normalResultContentView.isHidden = !isUnlocked
|
||||
vipMaskView.isHidden = isUnlocked
|
||||
vipOfferImageView.isHidden = isUnlocked
|
||||
vipResultHeightConstraint?.isActive = !isUnlocked
|
||||
actionBtn.setBackgroundImage(
|
||||
UIImage(named: isUnlocked ? "Common/button_bg_2" : "VipFeature/wide_button_bg"),
|
||||
for: .normal
|
||||
)
|
||||
func updateSubViews() {
|
||||
normalResultContentView.isHidden = false
|
||||
}
|
||||
|
||||
lazy var navBgView: UIImageView = {
|
||||
|
|
@ -85,7 +77,6 @@ class SearchLocationResultView: UIView {
|
|||
resultCard.layoutChain
|
||||
.topToBottomOfView(headerView, offset: 20)
|
||||
.edgesHorzontal(15)
|
||||
vipResultHeightConstraint = resultCard.heightAnchor.constraint(equalToConstant: 158)
|
||||
|
||||
contentView.addSubview(actionBtn)
|
||||
actionBtn.layoutChain
|
||||
|
|
@ -94,15 +85,6 @@ class SearchLocationResultView: UIView {
|
|||
.height(56)
|
||||
.bottom(kSafeBottomMargin + 30)
|
||||
|
||||
contentView.addSubview(vipOfferImageView)
|
||||
vipOfferImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
vipOfferImageView.trailingAnchor.constraint(equalTo: actionBtn.trailingAnchor, constant: -6),
|
||||
vipOfferImageView.bottomAnchor.constraint(equalTo: actionBtn.topAnchor, constant: 10),
|
||||
vipOfferImageView.widthAnchor.constraint(equalToConstant: 94),
|
||||
vipOfferImageView.heightAnchor.constraint(equalToConstant: 32)
|
||||
])
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
|
|
@ -115,8 +97,6 @@ class SearchLocationResultView: UIView {
|
|||
resultStackView.layoutChain.edges()
|
||||
resultStackView.addArrangedSubview(normalResultContentView)
|
||||
|
||||
card.addSubview(vipMaskView)
|
||||
vipMaskView.layoutChain.edges()
|
||||
return card
|
||||
}()
|
||||
|
||||
|
|
@ -233,13 +213,6 @@ class SearchLocationResultView: UIView {
|
|||
return btn
|
||||
}()
|
||||
|
||||
private lazy var vipOfferImageView: UIImageView = {
|
||||
let view = UIImageView(image: UIImage(named: "VipFeature/limited_offer"))
|
||||
view.contentMode = .scaleAspectFit
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
var inviteBtn: UIButton { actionBtn }
|
||||
|
||||
lazy var inviteView: UIView = {
|
||||
|
|
@ -283,13 +256,6 @@ class SearchLocationResultView: UIView {
|
|||
return btn
|
||||
}()
|
||||
|
||||
lazy var vipMaskView: UIButton = {
|
||||
let view = UIButton(type: .custom)
|
||||
view.setBackgroundImage(UIImage(named: "VipFeature/realtime_location"), for: .normal)
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: .zero)
|
||||
backgroundColor = .white
|
||||
|
|
|
|||
|
|
@ -130,12 +130,6 @@ final class TodayTrackDetailVC: BaseViewController {
|
|||
})
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
rootView.vipRequiredBtn.rx.tap
|
||||
.subscribe(onNext: {
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
viewModel.displayedTrips
|
||||
.observe(on: MainScheduler.instance)
|
||||
.subscribe(onNext: { [weak self] trips in
|
||||
|
|
@ -146,22 +140,7 @@ final class TodayTrackDetailVC: BaseViewController {
|
|||
viewModel.isEmpty
|
||||
.observe(on: MainScheduler.instance)
|
||||
.subscribe(onNext: { [weak self] empty in
|
||||
guard let self, !self.viewModel.isVipRequired.value else { return }
|
||||
self.rootView.emptyLab.isHidden = !empty
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
viewModel.isVipRequired
|
||||
.observe(on: MainScheduler.instance)
|
||||
.distinctUntilChanged()
|
||||
.subscribe(onNext: { [weak self] required in
|
||||
guard let self else { return }
|
||||
self.stopPlayback(resetProgress: true)
|
||||
self.rootView.setVipRequired(required)
|
||||
self.updateSheetHeight(isVipRequired: required)
|
||||
if !required {
|
||||
self.rootView.emptyLab.isHidden = !self.viewModel.isEmpty.value
|
||||
}
|
||||
self?.rootView.emptyLab.isHidden = !empty
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
|
|
@ -202,14 +181,6 @@ final class TodayTrackDetailVC: BaseViewController {
|
|||
|
||||
}
|
||||
|
||||
private func updateSheetHeight(isVipRequired: Bool) {
|
||||
guard let constraint = rootView.sheetHeightConstraint else { return }
|
||||
let minHeight: CGFloat = 200 + kSafeBottomMargin
|
||||
let maxHeight: CGFloat = UIScreen.main.bounds.height * 0.7
|
||||
let vipHeight: CGFloat = UIScreen.main.bounds.height * 0.47
|
||||
constraint.constant = isVipRequired ? min(maxHeight, max(minHeight, vipHeight)) : minHeight
|
||||
}
|
||||
|
||||
private func bindSheetPan() {
|
||||
let pan = UIPanGestureRecognizer()
|
||||
rootView.sheetView.addGestureRecognizer(pan)
|
||||
|
|
|
|||
|
|
@ -150,14 +150,6 @@ final class TodayTrackDetailView: UIView {
|
|||
return lab
|
||||
}()
|
||||
|
||||
let vipRequiredBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setImage(UIImage(named: "Home/HistoryTrack/vip_required"), for: .normal)
|
||||
btn.imageView?.contentMode = .scaleAspectFit
|
||||
btn.isHidden = true
|
||||
return btn
|
||||
}()
|
||||
|
||||
private(set) var sheetHeightConstraint: NSLayoutConstraint?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
|
|
@ -272,19 +264,6 @@ final class TodayTrackDetailView: UIView {
|
|||
.centerX()
|
||||
.topToBottomOfView(dateBar, offset: 40)
|
||||
|
||||
sheetView.addSubview(vipRequiredBtn)
|
||||
vipRequiredBtn.layoutChain
|
||||
.topToBottomOfView(dateBar, offset: 28)
|
||||
.centerX()
|
||||
.width(303)
|
||||
.height(139)
|
||||
}
|
||||
|
||||
func setVipRequired(_ required: Bool) {
|
||||
vipRequiredBtn.isHidden = !required
|
||||
timelineTV.isHidden = required
|
||||
emptyLab.isHidden = true
|
||||
playbackBar.isHidden = required
|
||||
}
|
||||
|
||||
func cleanupMap() {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ final class TodayTrackDetailViewModel {
|
|||
let selectedTripId = BehaviorRelay<String?>(value: nil)
|
||||
let displayedTrips = BehaviorRelay<[ScheduleRecordModel]>(value: [])
|
||||
let isEmpty = BehaviorRelay<Bool>(value: true)
|
||||
let isVipRequired = BehaviorRelay<Bool>(value: false)
|
||||
let loading = BehaviorRelay<Bool>(value: false)
|
||||
|
||||
private let disposeBag = DisposeBag()
|
||||
|
|
@ -76,7 +75,6 @@ final class TodayTrackDetailViewModel {
|
|||
selectedTripId.accept(nil)
|
||||
trips.accept([])
|
||||
displayedTrips.accept([])
|
||||
isVipRequired.accept(false)
|
||||
isEmpty.accept(false)
|
||||
loadTrips(userId: userId, dateKey: dateKey)
|
||||
}
|
||||
|
|
@ -95,19 +93,14 @@ final class TodayTrackDetailViewModel {
|
|||
.subscribe(onNext: { [weak self] response in
|
||||
guard let self, self.requestToken == token else { return }
|
||||
self.applyTrips(response.trips)
|
||||
}, onError: { [weak self] error in
|
||||
}, onError: { [weak self] _ in
|
||||
guard let self, self.requestToken == token else { return }
|
||||
if error.gatewayStatusCode == 22000 {
|
||||
self.applyVipRequired()
|
||||
return
|
||||
}
|
||||
self.applyTrips([])
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func applyTrips(_ list: [ScheduleRecordModel]) {
|
||||
isVipRequired.accept(false)
|
||||
let sorted = list.sorted { $0.start_time < $1.start_time }
|
||||
trips.accept(sorted)
|
||||
if selectedTripId.value == nil || !sorted.contains(where: { $0.id == selectedTripId.value }) {
|
||||
|
|
@ -117,15 +110,6 @@ final class TodayTrackDetailViewModel {
|
|||
loading.accept(false)
|
||||
}
|
||||
|
||||
private func applyVipRequired() {
|
||||
selectedTripId.accept(nil)
|
||||
trips.accept([])
|
||||
displayedTrips.accept([])
|
||||
isEmpty.accept(false)
|
||||
isVipRequired.accept(true)
|
||||
loading.accept(false)
|
||||
}
|
||||
|
||||
private func applyDisplayedTrips() {
|
||||
let all = trips.value
|
||||
if let id = selectedTripId.value, let one = all.first(where: { $0.id == id }) {
|
||||
|
|
|
|||
|
|
@ -12,14 +12,6 @@ import RxCocoa
|
|||
|
||||
class LaunchViewController: BaseViewController {
|
||||
|
||||
private let guideImgList: [String] = [
|
||||
"guide_1",
|
||||
"guide_2",
|
||||
"guide_3",
|
||||
"guide_4",
|
||||
"guide_5"
|
||||
]
|
||||
|
||||
private let showVersion = 1
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
|
@ -28,31 +20,9 @@ class LaunchViewController: BaseViewController {
|
|||
// Do any additional setup after loading the view.
|
||||
view.backgroundColor = .white
|
||||
setupLayout()
|
||||
|
||||
experienceBtn.rx.tap.subscribe(onNext: { _ in
|
||||
Defaults[\.isShowGuide] = false
|
||||
AppDelegate.shared.showMainViewController()
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
getUserConfig()
|
||||
}
|
||||
|
||||
private func navigateAfterDelay() {
|
||||
Observable.just(())
|
||||
.delay(.milliseconds(2500), scheduler: MainScheduler.instance)
|
||||
.subscribe(onNext: { [weak self] in
|
||||
guard let systemConfig = AppContextManager.shared.systemConfig else { return }
|
||||
if systemConfig.guideDisplay, Defaults[\.isShowGuide] {
|
||||
self?.collectionView.isHidden = false
|
||||
self?.pageControl.isHidden = false
|
||||
self?.experienceBtn.isHidden = false
|
||||
} else {
|
||||
AppDelegate.shared.showMainViewController()
|
||||
}
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
/// 获取用户配置
|
||||
func getUserConfig() {
|
||||
SystemService.userConfig().subscribe(onNext: { response in
|
||||
|
|
@ -122,7 +92,7 @@ class LaunchViewController: BaseViewController {
|
|||
}
|
||||
|
||||
private func continueAfterConfig(model: UserConfigModel) {
|
||||
self.navigateAfterDelay()
|
||||
AppDelegate.shared.showMainViewController()
|
||||
}
|
||||
|
||||
private func showVersionUpgrade(_ upgrade: VersionUpgradeModel, model: UserConfigModel) {
|
||||
|
|
@ -155,62 +125,12 @@ class LaunchViewController: BaseViewController {
|
|||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
/// 引导页
|
||||
lazy var collectionView: UICollectionView = {
|
||||
let viewFlowLayout = UICollectionViewFlowLayout()
|
||||
viewFlowLayout.scrollDirection = .horizontal
|
||||
viewFlowLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||
viewFlowLayout.minimumLineSpacing = 0
|
||||
viewFlowLayout.minimumInteritemSpacing = 0
|
||||
viewFlowLayout.itemSize = CGSizeMake(kScreenWidth, kScreenHeight)
|
||||
|
||||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: viewFlowLayout)
|
||||
collectionView.backgroundColor = .white
|
||||
collectionView.dataSource = self
|
||||
collectionView.delegate = self
|
||||
collectionView.showsVerticalScrollIndicator = false
|
||||
collectionView.showsHorizontalScrollIndicator = false
|
||||
collectionView.isPagingEnabled = true
|
||||
collectionView.bounces = false
|
||||
collectionView.register(GuideCell.self)
|
||||
collectionView.isHidden = true
|
||||
return collectionView
|
||||
}()
|
||||
|
||||
lazy var experienceBtn: 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: "Guide/experience"), for: .normal)
|
||||
btn.isHidden = true
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var pageControl: JJPageControl = {
|
||||
let pageControl = JJPageControl(frame: CGRectMake(0, 0, kScreenWidth, 10))
|
||||
pageControl.currentColor = ThemeManager.shared.color.mainColor
|
||||
pageControl.currentPointSize = CGSize(width: 20, height: 6)
|
||||
pageControl.otherColor = ThemeManager.shared.color.mainColor.withAlphaComponent(0.5)
|
||||
pageControl.otherPointSize = CGSize(width: 6, height: 6)
|
||||
pageControl.pointCornerRadius = 3
|
||||
pageControl.controlSpacing = 18
|
||||
pageControl.pageAliment = .Center
|
||||
pageControl.backgroundColor = .clear
|
||||
pageControl.numberOfPages = guideImgList.count
|
||||
pageControl.isHidden = true
|
||||
return pageControl
|
||||
}()
|
||||
}
|
||||
|
||||
extension LaunchViewController {
|
||||
private func setupLayout() {
|
||||
view.addSubview(launchLogoView)
|
||||
view.addSubview(tipsImgView)
|
||||
view.addSubview(collectionView)
|
||||
view.addSubview(experienceBtn)
|
||||
view.addSubview(pageControl)
|
||||
|
||||
launchLogoView.layoutChain
|
||||
.centerX()
|
||||
|
|
@ -219,74 +139,5 @@ extension LaunchViewController {
|
|||
tipsImgView.layoutChain
|
||||
.centerX()
|
||||
.bottom(kSafeBottomMargin + 50)
|
||||
|
||||
collectionView.layoutChain.edges()
|
||||
|
||||
experienceBtn.layoutChain
|
||||
.bottom(kSafeBottomMargin + 23)
|
||||
.width(245)
|
||||
.height(50)
|
||||
.centerX()
|
||||
|
||||
pageControl.layoutChain
|
||||
.bottomToTopOfView(experienceBtn, offset: -25)
|
||||
.edgesHorzontal(10)
|
||||
.centerX()
|
||||
.height(10)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDataSource
|
||||
extension LaunchViewController: UICollectionViewDataSource {
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
guideImgList.count
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cell: GuideCell = collectionView.dequeueReusableCell(for: indexPath)
|
||||
cell.configure(imageName: guideImgList[indexPath.row])
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDelegate
|
||||
extension LaunchViewController: UICollectionViewDelegate {
|
||||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
if scrollView == collectionView {
|
||||
let page = Int(scrollView.contentOffset.x / scrollView.dl.width)
|
||||
pageControl.currentPage = page
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GuideCell: UICollectionViewCell {
|
||||
|
||||
func configure(imageName: String) {
|
||||
imgView.image = UIImage(named: "Guide/" + imageName)
|
||||
}
|
||||
|
||||
private func setupSubViews() {
|
||||
addSubview(imgView)
|
||||
setupLayout()
|
||||
}
|
||||
|
||||
private func setupLayout() {
|
||||
imgView.layoutChain.edges()
|
||||
}
|
||||
|
||||
lazy var imgView: UIImageView = {
|
||||
let icon = UIImageView()
|
||||
icon.contentMode = .scaleAspectFill
|
||||
return icon
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
contentView.backgroundColor = .clear
|
||||
setupSubViews()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,10 @@ final class LockDistractVC: BaseViewController {
|
|||
private var groupModel: GroupModel?
|
||||
private var ownedGroups: [GroupInfoModel] = []
|
||||
private var activeGroupKey: String = ""
|
||||
private var groupVip = GroupMemberVip()
|
||||
private var members: [GroupMemberModel] = []
|
||||
private var selectedIndex = 0
|
||||
private var lockAppsDisposable: Disposable?
|
||||
private let lockStateSubscriptionOwner = UUID()
|
||||
private var hasAppeared = false
|
||||
|
||||
override var isNavigationBarHidden: Bool { true }
|
||||
|
||||
|
|
@ -31,18 +29,9 @@ final class LockDistractVC: BaseViewController {
|
|||
super.viewDidLoad()
|
||||
view.backgroundColor = UIColor(hexStr: "#FAFAFA")
|
||||
bind()
|
||||
applyGroupVip(groupVip)
|
||||
loadGroupMembers()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
if hasAppeared {
|
||||
refreshGroupVip()
|
||||
}
|
||||
hasAppeared = true
|
||||
}
|
||||
|
||||
@MainActor deinit {
|
||||
MQTTService.shared.updateLockStateSubscriptions(
|
||||
owner: lockStateSubscriptionOwner,
|
||||
|
|
@ -156,7 +145,6 @@ final class LockDistractVC: BaseViewController {
|
|||
|
||||
private func loadUsers(for groupKey: String) {
|
||||
guard !groupKey.isEmpty else {
|
||||
applyGroupVip(GroupMemberVip())
|
||||
applyMembers([])
|
||||
return
|
||||
}
|
||||
|
|
@ -167,7 +155,6 @@ final class LockDistractVC: BaseViewController {
|
|||
return
|
||||
}
|
||||
guard let self, self.activeGroupKey == groupKey else { return }
|
||||
self.applyGroupVip(response.vip)
|
||||
self.applyMembers(response.list)
|
||||
}, onError: { error in
|
||||
DLToast.showError(error)
|
||||
|
|
@ -187,28 +174,6 @@ final class LockDistractVC: BaseViewController {
|
|||
loadLockableApps()
|
||||
}
|
||||
|
||||
private var isAppLockVipUnlocked: Bool {
|
||||
groupVip.app_lock == true
|
||||
}
|
||||
|
||||
private func applyGroupVip(_ vip: GroupMemberVip) {
|
||||
groupVip = vip
|
||||
rootView.updateVipAccess(isUnlocked: isAppLockVipUnlocked)
|
||||
}
|
||||
|
||||
private func refreshGroupVip() {
|
||||
let groupKey = activeGroupKey
|
||||
guard !groupKey.isEmpty else { return }
|
||||
GroupService.groupUsers(groupKey: groupKey)
|
||||
.subscribe(onNext: { [weak self] response in
|
||||
guard let self,
|
||||
self.activeGroupKey == groupKey,
|
||||
response.isValid(for: groupKey) else { return }
|
||||
self.applyGroupVip(response.vip)
|
||||
}, onError: { _ in })
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func stepMember(_ delta: Int) {
|
||||
guard !members.isEmpty else { return }
|
||||
let count = members.count
|
||||
|
|
@ -326,9 +291,8 @@ final class LockDistractVC: BaseViewController {
|
|||
}
|
||||
DLToast.showLoading()
|
||||
UserService.requestPhoneUnlock(os: os, groupKey: activeGroupKey, tokens: tokens)
|
||||
.subscribe(onNext: { [weak self] _ in
|
||||
.subscribe(onNext: { _ in
|
||||
DLToast.show(text: "已向圈主发送解锁请求")
|
||||
self?.refreshGroupVip()
|
||||
}, onError: { error in
|
||||
DLToast.show(error, fallbackText: "发送失败")
|
||||
})
|
||||
|
|
@ -359,7 +323,6 @@ final class LockDistractVC: BaseViewController {
|
|||
self?.rootView.lockBtn.isEnabled = true
|
||||
DLToast.show(text: "已解锁")
|
||||
self?.loadLockableApps()
|
||||
self?.refreshGroupVip()
|
||||
}, onError: { [weak self] error in
|
||||
self?.rootView.lockBtn.isEnabled = true
|
||||
DLToast.show(error, fallbackText: "解锁失败")
|
||||
|
|
@ -368,10 +331,6 @@ final class LockDistractVC: BaseViewController {
|
|||
}
|
||||
|
||||
private func performPrimaryAction() {
|
||||
if rootView.primaryAction == .lock, !isAppLockVipUnlocked {
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
return
|
||||
}
|
||||
switch rootView.primaryAction {
|
||||
case .requestUnlock:
|
||||
requestUnlockForLockedApps()
|
||||
|
|
@ -385,10 +344,6 @@ final class LockDistractVC: BaseViewController {
|
|||
}
|
||||
|
||||
private func lockSelectedApps() {
|
||||
guard isAppLockVipUnlocked else {
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
return
|
||||
}
|
||||
guard let member = selectedMember, !activeGroupKey.isEmpty else {
|
||||
DLToast.show(text: "请选择要锁定的成员")
|
||||
return
|
||||
|
|
@ -414,7 +369,6 @@ final class LockDistractVC: BaseViewController {
|
|||
self?.rootView.lockBtn.isEnabled = true
|
||||
DLToast.show(text: "锁定成功")
|
||||
self?.loadLockableApps()
|
||||
self?.refreshGroupVip()
|
||||
}, onError: { [weak self] error in
|
||||
self?.rootView.lockBtn.isEnabled = true
|
||||
DLToast.show(error, fallbackText: "锁定失败")
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ final class LockDistractView: UIView {
|
|||
private(set) var isLocked = false
|
||||
private(set) var isCurrentUser = false
|
||||
private(set) var primaryAction: LockDistractPrimaryAction = .lock
|
||||
private(set) var isVipUnlocked = true
|
||||
private let copyMaxLength = 20
|
||||
private var lockBtnHeightConstraint: NSLayoutConstraint?
|
||||
private var bottomStackToAppsConstraint: NSLayoutConstraint?
|
||||
|
|
@ -111,11 +110,6 @@ final class LockDistractView: UIView {
|
|||
appCollectionView.reloadData()
|
||||
}
|
||||
|
||||
func updateVipAccess(isUnlocked: Bool) {
|
||||
isVipUnlocked = isUnlocked
|
||||
applyLockedState()
|
||||
}
|
||||
|
||||
var selectedLockApps: [PhoneLockAppItem] {
|
||||
lockableApps.enumerated().compactMap { index, app in
|
||||
selectedAppIndexes.contains(index) ? app : nil
|
||||
|
|
@ -173,13 +167,12 @@ final class LockDistractView: UIView {
|
|||
appCollectionView.reloadData()
|
||||
lockBtn.isHidden = lockedByOthers
|
||||
lockBtnHeightConstraint?.constant = lockedByOthers ? 0 : 56
|
||||
vipOfferImageView.isHidden = lockedByOthers || primaryAction != .lock || isVipUnlocked
|
||||
switch primaryAction {
|
||||
case .lock:
|
||||
tipLab.text = "锁定功能需要在对方开启权限情况下才能使用哟~"
|
||||
lockBtn.setTitle(isVipUnlocked ? "锁定" : "开通会员 解锁功能", for: .normal)
|
||||
lockBtn.setTitle("锁定", for: .normal)
|
||||
lockBtn.setBackgroundImage(
|
||||
UIImage(named: isVipUnlocked ? "Common/button_bg_2" : "VipFeature/wide_button_bg"),
|
||||
UIImage(named: "Common/button_bg_2"),
|
||||
for: .normal
|
||||
)
|
||||
case .requestUnlock:
|
||||
|
|
@ -208,7 +201,6 @@ final class LockDistractView: UIView {
|
|||
addSubview(footerBar)
|
||||
footerBar.addSubview(tipRow)
|
||||
footerBar.addSubview(lockBtn)
|
||||
footerBar.addSubview(vipOfferImageView)
|
||||
|
||||
contentView.addSubview(navBgView)
|
||||
contentView.addSubview(statsRow)
|
||||
|
|
@ -244,14 +236,6 @@ final class LockDistractView: UIView {
|
|||
.bottomToTopOfView(lockBtn, offset: -8)
|
||||
.top(10)
|
||||
|
||||
vipOfferImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
vipOfferImageView.trailingAnchor.constraint(equalTo: lockBtn.trailingAnchor, constant: -6),
|
||||
vipOfferImageView.bottomAnchor.constraint(equalTo: lockBtn.topAnchor, constant: 10),
|
||||
vipOfferImageView.widthAnchor.constraint(equalToConstant: 94),
|
||||
vipOfferImageView.heightAnchor.constraint(equalToConstant: 32)
|
||||
])
|
||||
|
||||
scrollView.layoutChain
|
||||
.top()
|
||||
.edgesHorzontal()
|
||||
|
|
@ -727,13 +711,6 @@ final class LockDistractView: UIView {
|
|||
return btn
|
||||
}()
|
||||
|
||||
lazy var vipOfferImageView: UIImageView = {
|
||||
let view = UIImageView(image: UIImage(named: "VipFeature/limited_offer"))
|
||||
view.contentMode = .scaleAspectFit
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
private func makeSectionTitle(_ text: String) -> UIView {
|
||||
let row = UIView()
|
||||
let star = UIImageView(image: UIImage(named: "LockDistract/section_star"))
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ final class AboutVC: BaseViewController {
|
|||
}
|
||||
|
||||
@objc private func handleLogoTap() {
|
||||
#if DEBUG || AdHoc
|
||||
logoTapResetWork?.cancel()
|
||||
logoTapCount += 1
|
||||
if logoTapCount >= 5 {
|
||||
|
|
@ -75,6 +76,7 @@ final class AboutVC: BaseViewController {
|
|||
}
|
||||
logoTapResetWork = work
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: work)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func showServerSwitchAlert() {
|
||||
|
|
|
|||
|
|
@ -73,10 +73,9 @@ final class CancelAccountView: UIView {
|
|||
.bottom(20)
|
||||
|
||||
let specs: [(String, String)] = [
|
||||
("1. 放弃当前的VIP权益", "当前充值的VIP我们不会退款,请解除你的自动续费(如果有),同意请打勾"),
|
||||
("2. 当前账号没有违规及其他的法律纠纷", "同意请打勾"),
|
||||
("3. 确认是账号本人进行注销操作,自愿承担后果", "同意请打勾"),
|
||||
("4. 账号注销后账号不可登录,不可恢复", "同意请打勾")
|
||||
("1. 当前账号没有违规及其他的法律纠纷", "同意请打勾"),
|
||||
("2. 确认是账号本人进行注销操作,自愿承担后果", "同意请打勾"),
|
||||
("3. 账号注销后账号不可登录,不可恢复", "同意请打勾")
|
||||
]
|
||||
var previous: UIView?
|
||||
for spec in specs {
|
||||
|
|
@ -96,7 +95,7 @@ final class CancelAccountView: UIView {
|
|||
agreementBtn.titleLabel?.font = .systemFont(ofSize: 13, weight: .medium)
|
||||
agreementBtn.contentHorizontalAlignment = .left
|
||||
let agreementItem = CancelCheckItemView(
|
||||
title: "5. 账号注销协议",
|
||||
title: "4. 账号注销协议",
|
||||
actionButton: agreementBtn
|
||||
)
|
||||
checkItems.append(agreementItem)
|
||||
|
|
@ -104,7 +103,7 @@ final class CancelAccountView: UIView {
|
|||
agreementItem.layoutChain.topToBottomOfView(previous!).edgesHorzontal(14)
|
||||
|
||||
let tip6 = UILabel()
|
||||
tip6.text = "6. 请在输入框中输入以下文字"
|
||||
tip6.text = "5. 请在输入框中输入以下文字"
|
||||
tip6.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
tip6.textColor = UIColor(hexStr: "#293445")
|
||||
card.addSubview(tip6)
|
||||
|
|
|
|||
|
|
@ -13,30 +13,13 @@ final class MineView: UIView {
|
|||
|
||||
var disposeBag = DisposeBag()
|
||||
|
||||
private let vipCardHeight: CGFloat = 90
|
||||
private let settingsRowHeight: CGFloat = 52
|
||||
private let quickItemTitles = ["权限检测", "隐私设置", "状态设置", "账户设置"]
|
||||
|
||||
var onQuickTap: ((String) -> Void)?
|
||||
|
||||
func setupData(_ model: UserConfigModel) {
|
||||
guard AppContextManager.shared.systemConfig?.isIntercept == false else {
|
||||
maskBgView.isHidden = false
|
||||
return
|
||||
}
|
||||
maskBgView.isHidden = true
|
||||
guard model.vip > 1 else {
|
||||
vipTitleLab.text = "开通会员"
|
||||
vipContentLab.text = "会员尊享10+VIP特权"
|
||||
vipActivateButton.isHidden = false
|
||||
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)
|
||||
vipActivateButton.isHidden = model.vip == 3
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Components
|
||||
|
|
@ -72,55 +55,20 @@ final class MineView: UIView {
|
|||
MinePhotoWallView()
|
||||
}()
|
||||
|
||||
lazy var vipCardView: UIView = {
|
||||
lazy var cardView: UIView = {
|
||||
let view = UIView()
|
||||
view.layer.cornerRadius = 16
|
||||
view.clipsToBounds = true
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var vipCardBgImageView: UIImageView = {
|
||||
let iv = UIImageView()
|
||||
iv.image = UIImage(named: "Mine/vip_card")
|
||||
iv.contentMode = .scaleToFill
|
||||
return iv
|
||||
}()
|
||||
|
||||
lazy var maskBgView: UIImageView = {
|
||||
let iv = UIImageView()
|
||||
iv.image = UIImage(named: "Mine/mask_bg")
|
||||
iv.contentMode = .scaleToFill
|
||||
iv.isHidden = true
|
||||
return iv
|
||||
}()
|
||||
|
||||
lazy var vipTitleLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = FontManager.boboBold(18)
|
||||
label.textColor = .white
|
||||
label.text = " "
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var vipContentLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 12, weight: .medium)
|
||||
label.textColor = .white
|
||||
label.text = " "
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var vipActivateButton: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.backgroundColor = UIColor(hexStr: "#C0FF54")
|
||||
btn.setTitle("立即开通", for: .normal)
|
||||
btn.setTitleColor(UIColor(hexStr: "#00343B"), for: .normal)
|
||||
btn.titleLabel?.font = FontManager.boboBold(14)
|
||||
btn.cornerRadius = 9
|
||||
btn.isHidden = true
|
||||
return btn
|
||||
}()
|
||||
|
||||
private lazy var quickCardView: UIView = {
|
||||
let v = UIView()
|
||||
v.backgroundColor = .white
|
||||
|
|
@ -190,12 +138,8 @@ final class MineView: UIView {
|
|||
addSubview(navBgView)
|
||||
addSubview(photoWall)
|
||||
|
||||
contentView.addSubview(vipCardView)
|
||||
vipCardView.addSubview(vipCardBgImageView)
|
||||
vipCardView.addSubview(vipActivateButton)
|
||||
vipCardView.addSubview(vipTitleLab)
|
||||
vipCardView.addSubview(vipContentLab)
|
||||
vipCardView.addSubview(maskBgView)
|
||||
contentView.addSubview(cardView)
|
||||
cardView.addSubview(maskBgView)
|
||||
|
||||
contentView.addSubview(quickCardView)
|
||||
quickCardView.addSubview(quickStack)
|
||||
|
|
@ -230,29 +174,15 @@ final class MineView: UIView {
|
|||
.edges()
|
||||
.widthToView(scrollView)
|
||||
|
||||
vipCardView.layoutChain
|
||||
cardView.layoutChain
|
||||
.top(12)
|
||||
.left(16).right(16)
|
||||
.height(vipCardHeight)
|
||||
|
||||
vipCardBgImageView.layoutChain.edges()
|
||||
|
||||
vipTitleLab.layoutChain
|
||||
.top(22)
|
||||
.left(77)
|
||||
|
||||
vipContentLab.layoutChain
|
||||
.topToBottomOfView(vipTitleLab, offset: 6)
|
||||
.leftToView(vipTitleLab)
|
||||
|
||||
vipActivateButton.layoutChain
|
||||
.right(19).centerY()
|
||||
.width(70).height(28)
|
||||
.height(90)
|
||||
|
||||
maskBgView.layoutChain.edges()
|
||||
|
||||
quickCardView.layoutChain
|
||||
.topToBottomOfView(vipCardView, offset: 20)
|
||||
.topToBottomOfView(cardView, offset: 20)
|
||||
.left(16).right(16)
|
||||
.height(90)
|
||||
|
||||
|
|
|
|||
|
|
@ -56,12 +56,6 @@ final class MineViewController: BaseViewController {
|
|||
}
|
||||
|
||||
private func reactiveAction() {
|
||||
rootView.vipActivateButton.rx.tap
|
||||
.subscribe(onNext: { _ in
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
rootView.photoWall.onTapMember = { [weak self] member in
|
||||
self?.openMemberInfo(selectedUserId: member.user_id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ final class PigeonMessageVC: BaseViewController {
|
|||
private var rootView: PigeonMessageView!
|
||||
private var groupModel: GroupModel?
|
||||
private var activeGroupKey: String = ""
|
||||
private var groupVip = GroupMemberVip()
|
||||
private var members: [GroupMemberModel] = []
|
||||
private var selectedMembers: [GroupMemberModel] = []
|
||||
private var selectedImage: UIImage?
|
||||
|
|
@ -27,7 +26,6 @@ final class PigeonMessageVC: BaseViewController {
|
|||
private var playbackTimer: Timer?
|
||||
private var recordedDuration: TimeInterval = 0
|
||||
private var isPressingRecord = false
|
||||
private var hasAppeared = false
|
||||
|
||||
override func loadView() {
|
||||
rootView = PigeonMessageView(frame: UIScreen.main.bounds)
|
||||
|
|
@ -38,18 +36,9 @@ final class PigeonMessageVC: BaseViewController {
|
|||
super.viewDidLoad()
|
||||
bindActions()
|
||||
setupRecordingGesture()
|
||||
applyGroupVip(groupVip)
|
||||
loadMembers()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
if hasAppeared {
|
||||
refreshGroupVip()
|
||||
}
|
||||
hasAppeared = true
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
guard isMovingFromParent || navigationController?.isBeingDismissed == true else { return }
|
||||
|
|
@ -120,7 +109,6 @@ final class PigeonMessageVC: BaseViewController {
|
|||
private func loadUsers(for groupKey: String, showLoading: Bool) {
|
||||
guard !groupKey.isEmpty else {
|
||||
DLToast.dismiss()
|
||||
applyGroupVip(GroupMemberVip())
|
||||
applyMembers([])
|
||||
return
|
||||
}
|
||||
|
|
@ -133,7 +121,6 @@ final class PigeonMessageVC: BaseViewController {
|
|||
return
|
||||
}
|
||||
guard let self, self.activeGroupKey == groupKey else { return }
|
||||
self.applyGroupVip(response.vip)
|
||||
self.applyMembers(response.list)
|
||||
}, onError: { error in
|
||||
DLToast.dismiss()
|
||||
|
|
@ -148,28 +135,6 @@ final class PigeonMessageVC: BaseViewController {
|
|||
rootView.configureMembers(members, selectedIds: [])
|
||||
}
|
||||
|
||||
private var isPigeonVipUnlocked: Bool {
|
||||
groupVip.pigeon_mail == true
|
||||
}
|
||||
|
||||
private func applyGroupVip(_ vip: GroupMemberVip) {
|
||||
groupVip = vip
|
||||
rootView.updateVipAccess(isUnlocked: isPigeonVipUnlocked)
|
||||
}
|
||||
|
||||
private func refreshGroupVip() {
|
||||
let groupKey = activeGroupKey
|
||||
guard !groupKey.isEmpty else { return }
|
||||
GroupService.groupUsers(groupKey: groupKey)
|
||||
.subscribe(onNext: { [weak self] response in
|
||||
guard let self,
|
||||
self.activeGroupKey == groupKey,
|
||||
response.isValid(for: groupKey) else { return }
|
||||
self.applyGroupVip(response.vip)
|
||||
}, onError: { _ in })
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func updateCurrentGroupDisplay() {
|
||||
let groupName = groupModel?.groups.first {
|
||||
$0.group_key == activeGroupKey
|
||||
|
|
@ -516,10 +481,6 @@ final class PigeonMessageVC: BaseViewController {
|
|||
}
|
||||
|
||||
@objc private func sendMessage() {
|
||||
guard isPigeonVipUnlocked else {
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
return
|
||||
}
|
||||
if let wait = pigeonSendWaitSeconds() {
|
||||
DLToast.show(text: "发送过于频繁,请\(Self.formatWaitDuration(wait))后再试")
|
||||
return
|
||||
|
|
@ -619,9 +580,7 @@ final class PigeonMessageVC: BaseViewController {
|
|||
self.cleanupAudio(deleteFile: true)
|
||||
self.rootView.clearVoiceDraft()
|
||||
self.recordPigeonSendSuccess()
|
||||
DLToast.show(text: "发送成功") {
|
||||
self.refreshGroupVip()
|
||||
}
|
||||
DLToast.show(text: "发送成功")
|
||||
}, onError: { error in
|
||||
DLToast.dismiss()
|
||||
DLToast.show(error)
|
||||
|
|
|
|||
|
|
@ -82,7 +82,6 @@ final class PigeonMessageView: UIView, UITextFieldDelegate {
|
|||
private let recordingHintLabel = UILabel()
|
||||
private let voiceTimeLabel = UILabel()
|
||||
private let waveformView = PigeonWaveformView()
|
||||
private let vipOfferImageView = UIImageView(image: UIImage(named: "VipFeature/limited_offer"))
|
||||
private var selectedImageHeightConstraint: NSLayoutConstraint?
|
||||
private var isRecording = false
|
||||
|
||||
|
|
@ -253,15 +252,6 @@ final class PigeonMessageView: UIView, UITextFieldDelegate {
|
|||
updateMode()
|
||||
}
|
||||
|
||||
func updateVipAccess(isUnlocked: Bool) {
|
||||
sendButton.setTitle(isUnlocked ? "发送" : "开通会员 解锁功能", for: .normal)
|
||||
sendButton.setBackgroundImage(
|
||||
UIImage(named: isUnlocked ? "Common/button_bg_2" : "VipFeature/wide_button_bg"),
|
||||
for: .normal
|
||||
)
|
||||
vipOfferImageView.isHidden = isUnlocked
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
let headerView = PigeonHeaderGradientView()
|
||||
addSubview(headerView)
|
||||
|
|
@ -394,17 +384,6 @@ final class PigeonMessageView: UIView, UITextFieldDelegate {
|
|||
.topToBottomOfView(modeContainer, offset: 12)
|
||||
.edgesHorzontal(15)
|
||||
.bottom(16)
|
||||
|
||||
vipOfferImageView.contentMode = .scaleAspectFit
|
||||
vipOfferImageView.isHidden = true
|
||||
addSubview(vipOfferImageView)
|
||||
vipOfferImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
vipOfferImageView.trailingAnchor.constraint(equalTo: sendButton.trailingAnchor, constant: -6),
|
||||
vipOfferImageView.bottomAnchor.constraint(equalTo: sendButton.topAnchor, constant: 10),
|
||||
vipOfferImageView.widthAnchor.constraint(equalToConstant: 94),
|
||||
vipOfferImageView.heightAnchor.constraint(equalToConstant: 32)
|
||||
])
|
||||
}
|
||||
|
||||
private func setupRecipientCard(_ card: UIView) {
|
||||
|
|
|
|||
|
|
@ -1,195 +0,0 @@
|
|||
//
|
||||
// PopupQueueManager.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/7/3.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class PopupQueueManager {
|
||||
|
||||
private weak var sourceVC: UIViewController?
|
||||
private var currentStep = 0
|
||||
private var stepGeneration = 0
|
||||
private var countdownTimer: Timer?
|
||||
private var isPaused = false
|
||||
|
||||
init() {
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(handlePause),
|
||||
name: .pausePopupQueue,
|
||||
object: nil)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(handleResume),
|
||||
name: .resumePopupQueue,
|
||||
object: nil)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(handleInvalidate),
|
||||
name: .invalidatePopupQueue,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
func start(from vc: UIViewController) {
|
||||
sourceVC = vc
|
||||
currentStep = 0
|
||||
executeNextStep()
|
||||
}
|
||||
|
||||
@objc private func handlePause() {
|
||||
countdownTimer?.invalidate()
|
||||
countdownTimer = nil
|
||||
isPaused = true
|
||||
}
|
||||
|
||||
@objc private func handleResume() {
|
||||
guard isPaused else { return }
|
||||
isPaused = false
|
||||
executeNextStep()
|
||||
}
|
||||
|
||||
@objc private func handleInvalidate() {
|
||||
countdownTimer?.invalidate()
|
||||
countdownTimer = nil
|
||||
isPaused = true
|
||||
currentStep = 3
|
||||
}
|
||||
|
||||
private func executeNextStep() {
|
||||
guard AppContextManager.shared.systemConfig?.isIntercept == false else { return }
|
||||
switch currentStep {
|
||||
case 0:
|
||||
tryStartPromotionalActivities()
|
||||
case 1:
|
||||
tryStartSearchPopup()
|
||||
case 2:
|
||||
tryStartRegisterOrVipPopup()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Step 0: PromotionalActivities
|
||||
|
||||
private func tryStartPromotionalActivities() {
|
||||
guard let config = AppContextManager.shared.systemConfig else {
|
||||
skipToNext()
|
||||
return
|
||||
}
|
||||
guard config.popupConfig?.pay_pop == true,
|
||||
AppContextManager.shared.vip == 1,
|
||||
let timeConfig = config.timeConfig else {
|
||||
skipToNext()
|
||||
return
|
||||
}
|
||||
let gen = stepGeneration
|
||||
startCountdown(seconds: timeConfig.pay_pop_time) { [weak self] in
|
||||
guard let self = self, self.currentStep == 0, self.stepGeneration == gen else { return }
|
||||
/// TODO
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Step 1: Search PopupWindow
|
||||
|
||||
private func tryStartSearchPopup() {
|
||||
guard let config = AppContextManager.shared.systemConfig else {
|
||||
skipToNext()
|
||||
return
|
||||
}
|
||||
guard config.popupConfig?.search_phone == true,
|
||||
let timeConfig = config.timeConfig else {
|
||||
skipToNext()
|
||||
return
|
||||
}
|
||||
let gen = stepGeneration
|
||||
startCountdown(seconds: timeConfig.search_phone) {
|
||||
guard self.currentStep == 1, self.stepGeneration == gen else { return }
|
||||
PopupWindow.show(popupType: .search) {
|
||||
self.skipToNext()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Step 2: Register or VIP PopupWindow (mutually exclusive)
|
||||
|
||||
private func tryStartRegisterOrVipPopup() {
|
||||
guard let config = AppContextManager.shared.systemConfig,
|
||||
let timeConfig = config.timeConfig else {
|
||||
return
|
||||
}
|
||||
|
||||
if AppContextManager.shared.isGuest {
|
||||
let gen = stepGeneration
|
||||
startCountdown(seconds: timeConfig.signup_tip) {
|
||||
guard self.currentStep == 2, self.stepGeneration == gen else { return }
|
||||
PopupWindow.show(popupType: .register) {
|
||||
// done
|
||||
}
|
||||
}
|
||||
} else if AppContextManager.shared.vip == 1 {
|
||||
let gen = stepGeneration
|
||||
startCountdown(seconds: timeConfig.buy_vip_tip) {
|
||||
guard self.currentStep == 2, self.stepGeneration == gen else { return }
|
||||
PopupWindow.show(popupType: .vip) {
|
||||
// done
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func skipToNext() {
|
||||
stepGeneration += 1
|
||||
currentStep += 1
|
||||
executeNextStep()
|
||||
}
|
||||
|
||||
private func startCountdown(seconds: Int, onExpired: @escaping () -> Void) {
|
||||
guard seconds > 0 else {
|
||||
onExpired()
|
||||
return
|
||||
}
|
||||
countdownTimer?.invalidate()
|
||||
var remaining = seconds
|
||||
countdownTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
|
||||
remaining -= 1
|
||||
if remaining <= 0 {
|
||||
timer.invalidate()
|
||||
self?.countdownTimer = nil
|
||||
onExpired()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func push(_ vc: UIViewController) {
|
||||
let topVC = topViewController()
|
||||
guard let nav = topVC?.navigationController else { return }
|
||||
if nav.viewControllers.contains(where: { type(of: $0) == type(of: vc) }) {
|
||||
return
|
||||
}
|
||||
nav.pushViewController(vc, animated: true)
|
||||
}
|
||||
|
||||
private func topViewController() -> UIViewController? {
|
||||
guard let rootVC = kKeyWindow?.rootViewController else { return nil }
|
||||
return topViewController(from: rootVC)
|
||||
}
|
||||
|
||||
private func topViewController(from root: UIViewController) -> UIViewController {
|
||||
if let presented = root.presentedViewController {
|
||||
return topViewController(from: presented)
|
||||
}
|
||||
if let nav = root as? UINavigationController, let visible = nav.visibleViewController {
|
||||
return topViewController(from: visible)
|
||||
}
|
||||
if let tab = root as? UITabBarController, let selected = tab.selectedViewController {
|
||||
return topViewController(from: selected)
|
||||
}
|
||||
return root
|
||||
}
|
||||
}
|
||||
|
|
@ -110,16 +110,7 @@ class CreateScheduleVC: BaseViewController, MAMapViewDelegate {
|
|||
AppRouter.shared.popOrDismiss()
|
||||
}
|
||||
}, onError: { error in
|
||||
guard let code = error.underlyingError?.code else { return }
|
||||
|
||||
guard let systemConfig = AppContextManager.shared.systemConfig, systemConfig.isIntercept == false else { return }
|
||||
|
||||
if code == 20010 { // "创建的行程数达到上限"
|
||||
CreateScheduleVipPopView.show()
|
||||
}
|
||||
else {
|
||||
self.dl.show(error)
|
||||
}
|
||||
self.dl.show(error)
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,120 +0,0 @@
|
|||
//
|
||||
// CreateScheduleVipPopView.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
|
||||
class CreateScheduleVipPopView: UIView {
|
||||
|
||||
private static let shared = CreateScheduleVipPopView(frame: CGRect(origin: .zero, size: kScreenSize))
|
||||
|
||||
var disposeBag = DisposeBag()
|
||||
|
||||
static func show() {
|
||||
guard let superView = kKeyWindow else {
|
||||
return
|
||||
}
|
||||
|
||||
if CreateScheduleVipPopView.shared.superview != nil {
|
||||
CreateScheduleVipPopView.shared.removeFromSuperview()
|
||||
CreateScheduleVipPopView.shared.bgView.frame = .zero
|
||||
}
|
||||
CreateScheduleVipPopView.shared.bgView.alpha = 1
|
||||
CreateScheduleVipPopView.shared.bgView.frame = CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight)
|
||||
superView.addSubview(CreateScheduleVipPopView.shared)
|
||||
superView.bringSubviewToFront(CreateScheduleVipPopView.shared)
|
||||
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
CreateScheduleVipPopView.shared.bgView.alpha = 1
|
||||
}
|
||||
}
|
||||
|
||||
/// 关闭
|
||||
static func dismiss() {
|
||||
guard CreateScheduleVipPopView.shared.superview != nil else { return }
|
||||
|
||||
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseIn]) {
|
||||
CreateScheduleVipPopView.shared.bgView.alpha = 0
|
||||
} completion: { _ in
|
||||
CreateScheduleVipPopView.shared.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
private func setupRx() {
|
||||
upgradedBtn.rx.tap.subscribe(onNext: { _ in
|
||||
CreateScheduleVipPopView.dismiss()
|
||||
AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.homeUrl])
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
closeBtn.rx.tap.subscribe(onNext: { _ in
|
||||
CreateScheduleVipPopView.dismiss()
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private lazy var bgView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .black.withAlphaComponent(0.5)
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var vipImgView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.image = UIImage(named: "Schedule/vip_pop")
|
||||
view.contentMode = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var upgradedBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.backgroundColor = .clear
|
||||
btn.setBackgroundImage(UIImage(named: "Schedule/upgrade_bg"), for: .normal)
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var closeBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.backgroundColor = .clear
|
||||
btn.setBackgroundImage(UIImage(named: "Group/close"), for: .normal)
|
||||
btn.extendEdgeInsets = UIEdgeInsets(top: 10, left: 100, bottom: 100, right: 100)
|
||||
return btn
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .clear
|
||||
addSubview(bgView)
|
||||
bgView.addSubview(vipImgView)
|
||||
bgView.addSubview(upgradedBtn)
|
||||
bgView.addSubview(closeBtn)
|
||||
|
||||
vipImgView.layoutChain
|
||||
.centerY()
|
||||
.edgesHorzontal(25)
|
||||
.heightToWidth(814/648)
|
||||
|
||||
upgradedBtn.layoutChain
|
||||
.topToBottomOfView(vipImgView, offset: -20)
|
||||
.centerX()
|
||||
.width(240)
|
||||
.height(60)
|
||||
|
||||
closeBtn.layoutChain
|
||||
.topToBottomOfView(upgradedBtn, offset: 15)
|
||||
.centerX()
|
||||
.width(22)
|
||||
.height(22)
|
||||
|
||||
setupRx()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
//
|
||||
// ScheduleVC.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
import RxDataSources
|
||||
import MJRefresh
|
||||
import ObjectMapper
|
||||
|
||||
class ScheduleVC: BaseViewController {
|
||||
|
||||
fileprivate var rootView: ScheduleView!
|
||||
|
||||
override func loadView() {
|
||||
rootView = ScheduleView(frame: UIScreen.main.bounds)
|
||||
view = rootView
|
||||
}
|
||||
|
||||
private var viewModel = ScheduleViewModel()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
rootView.allCheckBtn.isSelected = true
|
||||
setupTableViewHeaderFooter()
|
||||
bindViewModel()
|
||||
reactiveAction()
|
||||
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
requestFollowList()
|
||||
requestData()
|
||||
}
|
||||
|
||||
private func bindViewModel() {
|
||||
viewModel.output.sectionedItems
|
||||
.bind(to: rootView.collectionView.rx.items(dataSource: dataSource))
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
viewModel.output.scheduleSectionedItems
|
||||
.bind(to: rootView.tableView.rx.items(dataSource: tableViewDataSource))
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
viewModel.output.refreshResult.subscribe(onNext: { [weak self] (status, isEmpty) in
|
||||
guard let self = self else { return }
|
||||
self.dl.dismiss()
|
||||
self.rootView.tableView.refresh(status: status, isEmpty: isEmpty)
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
viewModel.output.sectionedItems
|
||||
.map { !($0.first?.items.isEmpty ?? true) }
|
||||
.bind(to: rootView.emptyVisitorsLabel.rx.isHidden)
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
viewModel.output.scheduleSectionedItems
|
||||
.map { !($0.first?.items.isEmpty ?? true) }
|
||||
.bind(to: rootView.emptyScheduleLabel.rx.isHidden)
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
rootView.collectionView.rx.modelSelected(ViewedModel.self)
|
||||
.subscribe(viewModel.viewedCellAction.inputs)
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
rootView.tableView.rx.modelSelected(ScheduleModel.self)
|
||||
.subscribe(viewModel.cellAction.inputs)
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func reactiveAction() {
|
||||
// 全部
|
||||
rootView.allCheckBtn.rx.tap.subscribe(onNext: { [weak self] _ in
|
||||
guard let self = self, !self.rootView.allCheckBtn.isSelected else { return }
|
||||
self.rootView.allCheckBtn.isSelected = true
|
||||
self.rootView.followedCheckBtn.isSelected = false
|
||||
self.rootView.mineCheckBtn.isSelected = false
|
||||
self.changeCondition(follow: false, own: false)
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
// 我关注的
|
||||
rootView.followedCheckBtn.rx.tap.subscribe(onNext: { [weak self] _ in
|
||||
guard let self = self, !self.rootView.followedCheckBtn.isSelected else { return }
|
||||
self.rootView.allCheckBtn.isSelected = false
|
||||
self.rootView.followedCheckBtn.isSelected = true
|
||||
self.rootView.mineCheckBtn.isSelected = false
|
||||
self.changeCondition(follow: true, own: false)
|
||||
}).disposed(by: disposeBag)
|
||||
|
||||
// 我的
|
||||
rootView.mineCheckBtn.rx.tap.subscribe(onNext: { [weak self] _ in
|
||||
guard let self = self, !self.rootView.mineCheckBtn.isSelected else { return }
|
||||
self.rootView.allCheckBtn.isSelected = false
|
||||
self.rootView.followedCheckBtn.isSelected = false
|
||||
self.rootView.mineCheckBtn.isSelected = true
|
||||
self.changeCondition(follow: false, own: true)
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func setupTableViewHeaderFooter() {
|
||||
// 设置刷新header/footer
|
||||
let header = MJRefreshNormalHeader(refreshingBlock: { [weak self] in
|
||||
self?.viewModel.refresh()
|
||||
})
|
||||
header.addFeedbackGenerator()
|
||||
header.stateLabel?.isHidden = true
|
||||
rootView.tableView.mj_header = header
|
||||
|
||||
let footer = MJRefreshAutoNormalFooter(refreshingBlock: viewModel.loadMore)
|
||||
footer.isHidden = true
|
||||
rootView.tableView.mj_footer = footer
|
||||
}
|
||||
|
||||
// MARK: - API
|
||||
private func requestFollowList() {
|
||||
DLToast.showLoading()
|
||||
UserService.followList().subscribe(onNext: { response in
|
||||
DLToast.dismiss()
|
||||
self.viewModel.loadViewedData(response.list)
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func requestSetFollow(id: String, op: Bool) {
|
||||
DLToast.showLoading()
|
||||
ItineraryService.follow(id: id, op: op ? 2 : 1).subscribe(onNext: { response in
|
||||
DLToast.dismiss()
|
||||
DLToast.show(text: op ? "已取消" : "已关注此行程, \n行程路线将自动添加到您的行程列表。")
|
||||
self.requestData()
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
// MARK: - API 行程列表
|
||||
private func requestData() {
|
||||
viewModel.refresh()
|
||||
}
|
||||
|
||||
// 更换查询条件
|
||||
private func changeCondition(follow: Bool, own: Bool) {
|
||||
viewModel.changeCondition(follow: follow, own: own)
|
||||
requestData()
|
||||
}
|
||||
|
||||
// MARK: - dataSource
|
||||
private lazy var dataSource: RxCollectionViewSectionedReloadDataSource<ViewedListSectionModel> = {
|
||||
RxCollectionViewSectionedReloadDataSource<ViewedListSectionModel> { datasource, collectionView, indexPath, model in
|
||||
let cell: ViewedCell = collectionView.dequeueReusableCell(for: indexPath)
|
||||
cell.configure(model)
|
||||
return cell
|
||||
}
|
||||
}()
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
lazy private var tableViewDataSource: RxTableViewSectionedReloadDataSource<ScheduleListSectionModel> = {
|
||||
return RxTableViewSectionedReloadDataSource<ScheduleListSectionModel>(
|
||||
configureCell: { (_, tableView, indexPath, model) in
|
||||
let cell: ScheduleListPopCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
cell.configure(model)
|
||||
|
||||
// 编辑
|
||||
cell.editBtn.rx.tap.subscribe(onNext: { _ in
|
||||
AppRouter.push(Route.createSchedule, userInfo: ["scheduleJson": model.toJSON()])
|
||||
}).disposed(by: cell.disposeBag)
|
||||
|
||||
// 关注
|
||||
cell.followBtn.rx.tap.subscribe(onNext: { _ in
|
||||
self.requestSetFollow(id: model.id, op: cell.followBtn.isSelected)
|
||||
}).disposed(by: cell.disposeBag)
|
||||
|
||||
return cell
|
||||
}
|
||||
)
|
||||
}()
|
||||
}
|
||||
|
|
@ -1,508 +0,0 @@
|
|||
//
|
||||
// ScheduleView.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
import TagListView
|
||||
|
||||
class ScheduleView: UIView {
|
||||
|
||||
var disposeBag = DisposeBag()
|
||||
|
||||
private func setupRx() {
|
||||
createBtn.rx.tap.subscribe(onNext: { _ in
|
||||
AppRouter.push(Route.createSchedule)
|
||||
}).disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
addSubview(navBgView)
|
||||
addSubview(navView)
|
||||
addSubview(headerView)
|
||||
addSubview(travelRouteView)
|
||||
addSubview(tableView)
|
||||
|
||||
addSubview(createBtn)
|
||||
|
||||
navBgView.layoutChain
|
||||
.edges(excludingEdge: .bottom)
|
||||
.height(kNaviHeight)
|
||||
|
||||
navView.layoutChain
|
||||
.edges(excludingEdge: .bottom)
|
||||
.height(kNaviHeight)
|
||||
|
||||
headerView.layoutChain
|
||||
.topToBottomOfView(navView)
|
||||
.edgesHorzontal()
|
||||
// .height(147)
|
||||
|
||||
travelRouteView.layoutChain
|
||||
.topToBottomOfView(headerView)
|
||||
.edgesHorzontal()
|
||||
|
||||
tableView.layoutChain
|
||||
.topToBottomOfView(travelRouteView)
|
||||
.edges(excludingEdge: .top)
|
||||
|
||||
createBtn.layoutChain
|
||||
.bottom(kSafeBottomMargin + 102)
|
||||
.right(10)
|
||||
.width(60)
|
||||
.heightToWidth(1)
|
||||
}
|
||||
|
||||
lazy var navBgView: UIImageView = {
|
||||
let iv = UIImageView()
|
||||
iv.image = UIImage(named: "Common/navBar_bg_1")
|
||||
iv.contentMode = .scaleAspectFill
|
||||
return iv
|
||||
}()
|
||||
|
||||
lazy var navView: BaseNavigationView = {
|
||||
let nav = BaseNavigationView(title: "行程")
|
||||
nav.backBtn.isHidden = true
|
||||
return nav
|
||||
}()
|
||||
|
||||
/// 谁看过我
|
||||
lazy var headerView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
let titleLab = UILabel()
|
||||
titleLab.text = "谁看过我"
|
||||
titleLab.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
titleLab.textColor = ThemeManager.shared.color.titleAuxColor
|
||||
view.addSubview(titleLab)
|
||||
titleLab.layoutChain
|
||||
.top(15)
|
||||
|
||||
let dotView = UIView()
|
||||
dotView.backgroundColor = UIColor(hexStr: "#16B3FF")
|
||||
dotView.cornerRadius = 2
|
||||
view.addSubview(dotView)
|
||||
dotView.layoutChain
|
||||
.left(15)
|
||||
.centerY(titleLab)
|
||||
.width(4)
|
||||
.height(11)
|
||||
|
||||
titleLab.layoutChain.leftToRightOfView(dotView, offset: 5)
|
||||
|
||||
let historyBtn = UIButton()
|
||||
historyBtn.setTitle("历史行程", for: .normal)
|
||||
historyBtn.setTitleColor(ThemeManager.shared.color.titleAuxColor, for: .normal)
|
||||
historyBtn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
||||
historyBtn.rx.tap.subscribe(onNext: { _ in
|
||||
AppRouter.push(Route.scheduleHistory)
|
||||
}).disposed(by: disposeBag)
|
||||
view.addSubview(historyBtn)
|
||||
historyBtn.layoutChain
|
||||
.right(15)
|
||||
.centerY(titleLab)
|
||||
historyBtn.sizeToFit()
|
||||
|
||||
view.addSubview(collectionView)
|
||||
collectionView.layoutChain
|
||||
.topToBottomOfView(titleLab, offset: 15)
|
||||
.edgesHorzontal()
|
||||
.height(80)
|
||||
.bottom(15)
|
||||
|
||||
// 暂无访客
|
||||
view.addSubview(emptyVisitorsLabel)
|
||||
emptyVisitorsLabel.layoutChain
|
||||
.centerY(collectionView)
|
||||
.centerX()
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var collectionView: UICollectionView = {
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.scrollDirection = .horizontal
|
||||
layout.itemSize = CGSize(width: 80, height: 80)
|
||||
layout.minimumLineSpacing = 15
|
||||
layout.sectionInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 15)
|
||||
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||
cv.backgroundColor = .clear
|
||||
cv.showsHorizontalScrollIndicator = false
|
||||
cv.register(ViewedCell.self)
|
||||
return cv
|
||||
}()
|
||||
|
||||
lazy var emptyVisitorsLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = "暂无访客"
|
||||
label.font = .systemFont(ofSize: 14)
|
||||
label.textColor = UIColor(hexStr: "#999999")
|
||||
label.textAlignment = .center
|
||||
label.isHidden = true
|
||||
return label
|
||||
}()
|
||||
|
||||
/// 行程路线
|
||||
lazy var travelRouteView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
let titleLab = UILabel()
|
||||
titleLab.text = "行程路线"
|
||||
titleLab.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
titleLab.textColor = ThemeManager.shared.color.titleAuxColor
|
||||
view.addSubview(titleLab)
|
||||
titleLab.layoutChain
|
||||
.top(15)
|
||||
.edgesVertical(5)
|
||||
|
||||
let dotView = UIView()
|
||||
dotView.backgroundColor = UIColor(hexStr: "#16B3FF")
|
||||
dotView.cornerRadius = 2
|
||||
view.addSubview(dotView)
|
||||
dotView.layoutChain
|
||||
.left(15)
|
||||
.centerY(titleLab)
|
||||
.width(4)
|
||||
.height(11)
|
||||
|
||||
titleLab.layoutChain.leftToRightOfView(dotView, offset: 5)
|
||||
|
||||
// 筛选 checkbox(从右到左)
|
||||
view.addSubview(allCheckBtn)
|
||||
view.addSubview(followedCheckBtn)
|
||||
view.addSubview(mineCheckBtn)
|
||||
|
||||
allCheckBtn.layoutChain
|
||||
.right(15)
|
||||
.centerY(titleLab)
|
||||
followedCheckBtn.layoutChain
|
||||
.rightToLeftOfView(allCheckBtn, offset: -20)
|
||||
.centerY(titleLab)
|
||||
mineCheckBtn.layoutChain
|
||||
.rightToLeftOfView(followedCheckBtn, offset: -20)
|
||||
.centerY(titleLab)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var allCheckBtn: UIButton = makeCheckButton(title: "全部")
|
||||
lazy var followedCheckBtn: UIButton = makeCheckButton(title: "我关注的")
|
||||
lazy var mineCheckBtn: UIButton = makeCheckButton(title: "我的")
|
||||
|
||||
private func makeCheckButton(title: String) -> UIButton {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setImage(UIImage(named: "Schedule/checkbox"), for: .normal)
|
||||
btn.setImage(UIImage(named: "Schedule/checkbox_selected"), for: .selected)
|
||||
btn.setTitle(" \(title)", for: .normal)
|
||||
btn.setTitleColor(UIColor(hexStr: "#333333"), for: .normal)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 13, weight: .medium)
|
||||
btn.sizeToFit()
|
||||
return btn
|
||||
}
|
||||
|
||||
lazy var tableView: UITableView = {
|
||||
let tableView = UITableView(frame: .zero, style: .plain)
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.separatorStyle = .none
|
||||
tableView.estimatedRowHeight = 80
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.showsVerticalScrollIndicator = false
|
||||
tableView.register(ScheduleListPopCell.self)
|
||||
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
|
||||
tableView.backgroundView = emptyScheduleLabel
|
||||
return tableView
|
||||
}()
|
||||
|
||||
lazy var emptyScheduleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = "暂无行程"
|
||||
label.font = .systemFont(ofSize: 14)
|
||||
label.textColor = UIColor(hexStr: "#999999")
|
||||
label.textAlignment = .center
|
||||
label.isHidden = true
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var createBtn: UIButton = {
|
||||
let btn = UIButton()
|
||||
btn.setImage(UIImage(named: "Schedule/create"), for: .normal)
|
||||
return btn
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: .zero)
|
||||
backgroundColor = .white
|
||||
setupUI()
|
||||
setupRx()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ViewedCell
|
||||
final class ViewedCell: UICollectionViewCell {
|
||||
|
||||
func configure(_ model: ViewedModel) {
|
||||
iconView.setHeadPic(model.displayHeadPic)
|
||||
|
||||
// 会员权益
|
||||
blurView.isHidden = AppContextManager.shared.vip > 1
|
||||
lockView.isHidden = AppContextManager.shared.vip > 1
|
||||
viewedCountLab.text = "看了\(model.count)次"
|
||||
}
|
||||
|
||||
lazy var iconView: UIImageView = {
|
||||
let iv = UIImageView()
|
||||
iv.contentMode = .scaleAspectFill
|
||||
iv.cornerRadius = 40
|
||||
iv.clipsToBounds = true
|
||||
iv.backgroundColor = .clear
|
||||
iv.addSubview(blurView)
|
||||
blurView.layoutChain.edges()
|
||||
|
||||
return iv
|
||||
}()
|
||||
|
||||
lazy var blurView: UIVisualEffectView = {
|
||||
let blurEffect = UIBlurEffect(style: .systemUltraThinMaterial)
|
||||
let view = UIVisualEffectView(effect: blurEffect)
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var lockView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
let lockIcon = UIImageView(image: UIImage(named: "Common/lock_white"))
|
||||
lockIcon.contentMode = .scaleAspectFill
|
||||
view.addSubview(lockIcon)
|
||||
lockIcon.layoutChain
|
||||
.top(17)
|
||||
.centerX()
|
||||
|
||||
view.addSubview(viewedCountLab)
|
||||
viewedCountLab.layoutChain
|
||||
.bottom(17)
|
||||
.edgesHorzontal()
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var viewedCountLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 12, weight: .medium)
|
||||
label.textColor = .white
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
contentView.addSubview(iconView)
|
||||
contentView.addSubview(lockView)
|
||||
|
||||
iconView.layoutChain
|
||||
.edges()
|
||||
|
||||
lockView.layoutChain
|
||||
.edges()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - ScheduleListPopCell
|
||||
class ScheduleListPopCell: UITableViewCell {
|
||||
|
||||
var disposeBag = DisposeBag()
|
||||
|
||||
func configure(_ model: ScheduleModel) {
|
||||
nameLab.text = model.nick_name + " 的行程路线"
|
||||
monthLab.text = getDateInterval2String(date: "\(model.timestamp/1000)", dateFormat: "MM月")
|
||||
editBtn.isHidden = !model.is_own
|
||||
followBtn.isHidden = model.is_own
|
||||
followBtn.isSelected = model.is_follow ? true : false
|
||||
|
||||
let groupNames = model.groups.map { $0.group_name }
|
||||
// addTags 内部立即用 frame.width 排标签,先给个预估宽度避免算错
|
||||
if !groupNames.isEmpty {
|
||||
let w = contentView.frame.width > 0 ? contentView.frame.width : UIScreen.main.bounds.width
|
||||
tagListView.frame.size.width = w - 170
|
||||
}
|
||||
tagListView.removeAllTags()
|
||||
tagListView.addTags(groupNames)
|
||||
tagListView.tagViews.forEach {
|
||||
$0.layer.cornerRadius = 2
|
||||
}
|
||||
tagListView.invalidateIntrinsicContentSize()
|
||||
|
||||
guard let pointModel = model.points.last else { return }
|
||||
dateLab.text = getDateInterval2String(date: "\(pointModel.expected_timestamp/1000)", dateFormat: "MM.dd")
|
||||
}
|
||||
|
||||
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(cornerView)
|
||||
cornerView.addSubview(nameLab)
|
||||
cornerView.addSubview(monthLab)
|
||||
cornerView.addSubview(dateLab)
|
||||
cornerView.addSubview(tagListView)
|
||||
cornerView.addSubview(editBtn)
|
||||
cornerView.addSubview(followBtn)
|
||||
|
||||
bgView.layoutChain
|
||||
.top(15).bottom(15)
|
||||
.edgesHorzontal(15)
|
||||
|
||||
cornerView.layoutChain.edges()
|
||||
|
||||
monthLab.layoutChain
|
||||
.top(15)
|
||||
.left(11)
|
||||
.width(35)
|
||||
|
||||
dateLab.layoutChain
|
||||
.topToBottomOfView(monthLab, offset: 4)
|
||||
.leftToView(monthLab)
|
||||
.width(35)
|
||||
|
||||
editBtn.layoutChain
|
||||
.top(15)
|
||||
.right(10)
|
||||
.width(70)
|
||||
.height(28)
|
||||
|
||||
nameLab.layoutChain
|
||||
.top(15)
|
||||
.leftToRightOfView(monthLab, offset: 15)
|
||||
|
||||
tagListView.layoutChain
|
||||
.leftToRightOfView(dateLab, offset: 15)
|
||||
.bottom(15)
|
||||
.rightToLeftOfView(editBtn, offset: -10)
|
||||
|
||||
nameLab.layoutChain.bottomToTopOfView(tagListView, offset:-5)
|
||||
|
||||
followBtn.layoutChain
|
||||
.topToView(editBtn)
|
||||
.rightToView(editBtn)
|
||||
.width(70)
|
||||
.height(28)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
// Initialization code
|
||||
}
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
super.setSelected(selected, animated: animated)
|
||||
|
||||
// Configure the view for the selected state
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
disposeBag = DisposeBag()
|
||||
}
|
||||
|
||||
lazy var bgView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
view.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.05).cgColor
|
||||
view.layer.shadowOffset = CGSize(width: 0, height: 0)
|
||||
view.layer.shadowOpacity = 1
|
||||
view.layer.shadowRadius = 8
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var cornerView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .white
|
||||
view.cornerRadius = 10
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var monthLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
||||
label.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var dateLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = ThemeManager.shared.color.subTitleColor
|
||||
label.font = .systemFont(ofSize: 12, weight: .regular)
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var nameLab: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
||||
label.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var editBtn: UIButton = {
|
||||
let btn = UIButton()
|
||||
btn.isHidden = true
|
||||
btn.setTitle("编辑", for: .normal)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
||||
btn.setTitleColor(UIColor(hexStr: "#0F2846"), for: .normal)
|
||||
btn.backgroundColor = UIColor(hexStr: "#EEFAFF")
|
||||
btn.borderWidth = 0.5
|
||||
btn.borderColor = UIColor(hexStr: "#16B3FF")
|
||||
btn.cornerRadius = 14
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var followBtn: UIButton = {
|
||||
let btn = UIButton()
|
||||
btn.isHidden = true
|
||||
btn.setTitle("关注", for: .normal)
|
||||
btn.setTitle("取消关注", for: .selected)
|
||||
btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
||||
btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
|
||||
btn.backgroundColor = .white
|
||||
btn.borderWidth = 0.5
|
||||
btn.borderColor = UIColor(hexStr: "#16B3FF")
|
||||
btn.cornerRadius = 14
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var tagListView: TagListView = {
|
||||
let view = TagListView()
|
||||
view.textFont = UIFont.systemFont(ofSize: 8, weight: .regular)
|
||||
view.textColor = UIColor(hexStr: "#B78C56")
|
||||
view.tagBackgroundColor = UIColor(hexStr: "#FFF3E4")
|
||||
view.paddingX = 6 // 水平内边距
|
||||
view.paddingY = 6 // 垂直内边距
|
||||
view.alignment = .left // 对齐
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
return view
|
||||
}()
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
//
|
||||
// OrderService.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/17.
|
||||
//
|
||||
|
||||
import RxSwift
|
||||
import Moya
|
||||
|
||||
struct OrderService {
|
||||
static let disposeBag = DisposeBag()
|
||||
|
||||
// /// 充值内容
|
||||
// /// - Parameters:
|
||||
// /// - type: 类型 member
|
||||
// static func rechargeInfo(type: String) -> Observable<VipExpenseResponse> {
|
||||
// let api = OrderAPI.rechargeInfo(type: type).multiTarget
|
||||
// return APIProvider.request(token: api)
|
||||
// .map(VipExpenseResponse.self)
|
||||
// .asObservable()
|
||||
// }
|
||||
|
||||
/// 支付参数
|
||||
/// - Parameters:
|
||||
/// - goodsId: 商品ID
|
||||
/// - payType: alipay、weixin
|
||||
/// - source: center
|
||||
/// - extra: 额外参数
|
||||
static func orderPayParams(goodsId: String, payType: String, source: String="center", extra: [String:Any]=[:]) -> Observable<ResponseModel> {
|
||||
let api = OrderAPI.orderPayParams(goodsId: goodsId, payType: payType, source: source, extra: extra).multiTarget
|
||||
return APIProvider.request(token: api)
|
||||
.map(ResponseModel.self)
|
||||
.asObservable()
|
||||
}
|
||||
|
||||
/// Apple 内购通知
|
||||
static func appleNotify(receipt: String, productId: String, transactionId: String) -> Observable<Void> {
|
||||
let api = OrderAPI.appleNotify(receipt: receipt, productId: productId, transactionId: transactionId).multiTarget
|
||||
return APIProvider.request(token: api)
|
||||
.map { _ in () }
|
||||
.asObservable()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,281 +0,0 @@
|
|||
//
|
||||
// PayResultPopVC.swift
|
||||
// QuickLocation
|
||||
//
|
||||
// Created by 八条 on 2026/6/17.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
import URLNavigator
|
||||
|
||||
class PayResultPopVC: DLCustomPopVC {
|
||||
|
||||
var disposeBag = DisposeBag()
|
||||
|
||||
// MARK: - Accessor
|
||||
public var confirmBlock: (() -> Void)?
|
||||
public var cancelBlock: (() -> Void)?
|
||||
|
||||
public var status: Bool? {
|
||||
didSet {
|
||||
guard let status = status else { return }
|
||||
logoImgView.image = UIImage(named: status ? "Popup/success" : "Popup/failure")
|
||||
}
|
||||
}
|
||||
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
|
||||
.centerX()
|
||||
.top(-44)
|
||||
.width(88)
|
||||
.height(88)
|
||||
|
||||
headerBgImg.layoutChain
|
||||
.edges(excludingEdge: .bottom)
|
||||
.heightToWidth(100/315)
|
||||
|
||||
var tempView: UIView?
|
||||
if titleText != nil,
|
||||
titleText?.isEmpty == false {
|
||||
titleLab.layoutChain
|
||||
.top(69)
|
||||
.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()
|
||||
view.backgroundColor = .clear
|
||||
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:
|
||||
/// - status: 支付状态 true false
|
||||
/// - title: 标题
|
||||
/// - message: 文本内容
|
||||
/// - confirmText: 确认按钮文案
|
||||
/// - confirmBlock: 确认按钮点击回调
|
||||
/// - cancelText: 取消按钮文案
|
||||
/// - cancelBlock: 取消按钮点击回调
|
||||
func showPayResultPop(showCloseBtn: Bool = true,
|
||||
status: Bool,
|
||||
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 = PayResultPopVC()
|
||||
vc.titleText = title
|
||||
vc.status = status
|
||||
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 showPayResultPop(showCloseBtn: Bool = true,
|
||||
status: Bool,
|
||||
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 = PayResultPopVC()
|
||||
vc.titleText = title
|
||||
vc.status = status
|
||||
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)
|
||||
}
|
||||
}
|
||||