Compare commits
No commits in common. "d3220a0f5a8c4a8aefac342fbd1fc21d1559ce74" and "e438736fb4e5852fc006f3223c9fbe54f0b85c44" have entirely different histories.
d3220a0f5a
...
e438736fb4
|
|
@ -176,24 +176,6 @@
|
||||||
307073E62FD18A20004C37CC /* GroupChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307073E22FD18A20004C37CC /* GroupChatVC.swift */; };
|
307073E62FD18A20004C37CC /* GroupChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307073E22FD18A20004C37CC /* GroupChatVC.swift */; };
|
||||||
307073EA2FD2715A004C37CC /* GroupChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307073E92FD2715A004C37CC /* GroupChatViewModel.swift */; };
|
307073EA2FD2715A004C37CC /* GroupChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307073E92FD2715A004C37CC /* GroupChatViewModel.swift */; };
|
||||||
30A7A9112FCAEE3D00105780 /* GroupListPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */; };
|
30A7A9112FCAEE3D00105780 /* GroupListPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */; };
|
||||||
30A87A4B2FEE1DAB0095E7C6 /* ItineraryDetailVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A4A2FEE1DAB0095E7C6 /* ItineraryDetailVC.swift */; };
|
|
||||||
30A87A4D2FEE1DB40095E7C6 /* ItineraryDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A4C2FEE1DB40095E7C6 /* ItineraryDetailView.swift */; };
|
|
||||||
30A87A502FEE4E4B0095E7C6 /* ScheduleHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A4F2FEE4E4B0095E7C6 /* ScheduleHistoryView.swift */; };
|
|
||||||
30A87A522FEE4E5D0095E7C6 /* ScheduleHistoryVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A512FEE4E5D0095E7C6 /* ScheduleHistoryVC.swift */; };
|
|
||||||
30A87A542FEE50B10095E7C6 /* ScheduleHistoryVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A532FEE50B10095E7C6 /* ScheduleHistoryVM.swift */; };
|
|
||||||
30A87A572FEE5A350095E7C6 /* ScheduleViewedVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A562FEE5A350095E7C6 /* ScheduleViewedVC.swift */; };
|
|
||||||
30A87A592FEE5A4C0095E7C6 /* ScheduleViewedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A582FEE5A4C0095E7C6 /* ScheduleViewedView.swift */; };
|
|
||||||
30A87A5B2FEE5AC20095E7C6 /* ScheduleViewedVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A5A2FEE5AC20095E7C6 /* ScheduleViewedVM.swift */; };
|
|
||||||
30A87A5E2FEE71A50095E7C6 /* CreateBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A5D2FEE71A50095E7C6 /* CreateBubbleView.swift */; };
|
|
||||||
30A87A602FEE71B80095E7C6 /* CreateBubbleVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A5F2FEE71B80095E7C6 /* CreateBubbleVC.swift */; };
|
|
||||||
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 */; };
|
|
||||||
30BAB84D2FCD2FDE00C33B5C /* InviteJoinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB84C2FCD2FDE00C33B5C /* InviteJoinView.swift */; };
|
30BAB84D2FCD2FDE00C33B5C /* InviteJoinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB84C2FCD2FDE00C33B5C /* InviteJoinView.swift */; };
|
||||||
30BAB84F2FCD2FED00C33B5C /* InviteJoinVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB84E2FCD2FED00C33B5C /* InviteJoinVC.swift */; };
|
30BAB84F2FCD2FED00C33B5C /* InviteJoinVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB84E2FCD2FED00C33B5C /* InviteJoinVC.swift */; };
|
||||||
30BAB8512FCD331C00C33B5C /* GroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8502FCD331C00C33B5C /* GroupAPI.swift */; };
|
30BAB8512FCD331C00C33B5C /* GroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8502FCD331C00C33B5C /* GroupAPI.swift */; };
|
||||||
|
|
@ -245,7 +227,6 @@
|
||||||
30DC185C2FD11E7A0041DCD1 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18572FD11E7A0041DCD1 /* WebViewController.swift */; };
|
30DC185C2FD11E7A0041DCD1 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18572FD11E7A0041DCD1 /* WebViewController.swift */; };
|
||||||
30DC185E2FD1211D0041DCD1 /* VipRightsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC185D2FD1211D0041DCD1 /* VipRightsVC.swift */; };
|
30DC185E2FD1211D0041DCD1 /* VipRightsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC185D2FD1211D0041DCD1 /* VipRightsVC.swift */; };
|
||||||
30DC18602FD12A020041DCD1 /* VipWaivePopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC185F2FD12A020041DCD1 /* VipWaivePopView.swift */; };
|
30DC18602FD12A020041DCD1 /* VipWaivePopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC185F2FD12A020041DCD1 /* VipWaivePopView.swift */; };
|
||||||
30EBF7202FEFD6F2009A8A87 /* GroupChooseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EBF71F2FEFD6F2009A8A87 /* GroupChooseView.swift */; };
|
|
||||||
30EFF2992FD65FB000EB35D4 /* VoicePlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF2982FD65FB000EB35D4 /* VoicePlayerManager.swift */; };
|
30EFF2992FD65FB000EB35D4 /* VoicePlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF2982FD65FB000EB35D4 /* VoicePlayerManager.swift */; };
|
||||||
30EFF29B2FD668C900EB35D4 /* VoiceRecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF29A2FD668C900EB35D4 /* VoiceRecordView.swift */; };
|
30EFF29B2FD668C900EB35D4 /* VoiceRecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF29A2FD668C900EB35D4 /* VoiceRecordView.swift */; };
|
||||||
30EFF3A42FD7C5A300EB35D4 /* GroupSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3A32FD7C5A300EB35D4 /* GroupSettingView.swift */; };
|
30EFF3A42FD7C5A300EB35D4 /* GroupSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3A32FD7C5A300EB35D4 /* GroupSettingView.swift */; };
|
||||||
|
|
@ -458,24 +439,6 @@
|
||||||
307073E32FD18A20004C37CC /* GroupChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatView.swift; sourceTree = "<group>"; };
|
307073E32FD18A20004C37CC /* GroupChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatView.swift; sourceTree = "<group>"; };
|
||||||
307073E92FD2715A004C37CC /* GroupChatViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatViewModel.swift; sourceTree = "<group>"; };
|
307073E92FD2715A004C37CC /* GroupChatViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatViewModel.swift; sourceTree = "<group>"; };
|
||||||
30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupListPopView.swift; sourceTree = "<group>"; };
|
30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupListPopView.swift; sourceTree = "<group>"; };
|
||||||
30A87A4A2FEE1DAB0095E7C6 /* ItineraryDetailVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItineraryDetailVC.swift; sourceTree = "<group>"; };
|
|
||||||
30A87A4C2FEE1DB40095E7C6 /* ItineraryDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItineraryDetailView.swift; sourceTree = "<group>"; };
|
|
||||||
30A87A4F2FEE4E4B0095E7C6 /* ScheduleHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleHistoryView.swift; sourceTree = "<group>"; };
|
|
||||||
30A87A512FEE4E5D0095E7C6 /* ScheduleHistoryVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleHistoryVC.swift; sourceTree = "<group>"; };
|
|
||||||
30A87A532FEE50B10095E7C6 /* ScheduleHistoryVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleHistoryVM.swift; sourceTree = "<group>"; };
|
|
||||||
30A87A562FEE5A350095E7C6 /* ScheduleViewedVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleViewedVC.swift; sourceTree = "<group>"; };
|
|
||||||
30A87A582FEE5A4C0095E7C6 /* ScheduleViewedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleViewedView.swift; sourceTree = "<group>"; };
|
|
||||||
30A87A5A2FEE5AC20095E7C6 /* ScheduleViewedVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleViewedVM.swift; sourceTree = "<group>"; };
|
|
||||||
30A87A5D2FEE71A50095E7C6 /* CreateBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubbleView.swift; sourceTree = "<group>"; };
|
|
||||||
30A87A5F2FEE71B80095E7C6 /* CreateBubbleVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubbleVC.swift; sourceTree = "<group>"; };
|
|
||||||
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>"; };
|
|
||||||
30BAB84C2FCD2FDE00C33B5C /* InviteJoinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteJoinView.swift; sourceTree = "<group>"; };
|
30BAB84C2FCD2FDE00C33B5C /* InviteJoinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteJoinView.swift; sourceTree = "<group>"; };
|
||||||
30BAB84E2FCD2FED00C33B5C /* InviteJoinVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteJoinVC.swift; sourceTree = "<group>"; };
|
30BAB84E2FCD2FED00C33B5C /* InviteJoinVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteJoinVC.swift; sourceTree = "<group>"; };
|
||||||
30BAB8502FCD331C00C33B5C /* GroupAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupAPI.swift; sourceTree = "<group>"; };
|
30BAB8502FCD331C00C33B5C /* GroupAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupAPI.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -528,7 +491,6 @@
|
||||||
30DC18572FD11E7A0041DCD1 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = "<group>"; };
|
30DC18572FD11E7A0041DCD1 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = "<group>"; };
|
||||||
30DC185D2FD1211D0041DCD1 /* VipRightsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipRightsVC.swift; sourceTree = "<group>"; };
|
30DC185D2FD1211D0041DCD1 /* VipRightsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipRightsVC.swift; sourceTree = "<group>"; };
|
||||||
30DC185F2FD12A020041DCD1 /* VipWaivePopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipWaivePopView.swift; sourceTree = "<group>"; };
|
30DC185F2FD12A020041DCD1 /* VipWaivePopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipWaivePopView.swift; sourceTree = "<group>"; };
|
||||||
30EBF71F2FEFD6F2009A8A87 /* GroupChooseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChooseView.swift; sourceTree = "<group>"; };
|
|
||||||
30EFF2982FD65FB000EB35D4 /* VoicePlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoicePlayerManager.swift; sourceTree = "<group>"; };
|
30EFF2982FD65FB000EB35D4 /* VoicePlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoicePlayerManager.swift; sourceTree = "<group>"; };
|
||||||
30EFF29A2FD668C900EB35D4 /* VoiceRecordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceRecordView.swift; sourceTree = "<group>"; };
|
30EFF29A2FD668C900EB35D4 /* VoiceRecordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceRecordView.swift; sourceTree = "<group>"; };
|
||||||
30EFF3A02FD7A47900EB35D4 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.storyboard"; sourceTree = "<group>"; };
|
30EFF3A02FD7A47900EB35D4 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.storyboard"; sourceTree = "<group>"; };
|
||||||
|
|
@ -968,8 +930,6 @@
|
||||||
30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */,
|
30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */,
|
||||||
30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */,
|
30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */,
|
||||||
30D87CDC2FDFF07500E958FD /* InteractionView.swift */,
|
30D87CDC2FDFF07500E958FD /* InteractionView.swift */,
|
||||||
30A87A692FEF59E60095E7C6 /* SearchLocation */,
|
|
||||||
30A87A5C2FEE711C0095E7C6 /* Bubble */,
|
|
||||||
30CCDE4F2FE2782700F5214A /* SignIn */,
|
30CCDE4F2FE2782700F5214A /* SignIn */,
|
||||||
30CCDE562FE39F6B00F5214A /* SOS */,
|
30CCDE562FE39F6B00F5214A /* SOS */,
|
||||||
);
|
);
|
||||||
|
|
@ -1256,60 +1216,6 @@
|
||||||
path = GroupChat;
|
path = GroupChat;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
30A87A492FEE1CB20095E7C6 /* ItineraryDetail */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
30A87A4A2FEE1DAB0095E7C6 /* ItineraryDetailVC.swift */,
|
|
||||||
30A87A4C2FEE1DB40095E7C6 /* ItineraryDetailView.swift */,
|
|
||||||
);
|
|
||||||
path = ItineraryDetail;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
30A87A4E2FEE4E390095E7C6 /* ScheduleHistory */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
30A87A512FEE4E5D0095E7C6 /* ScheduleHistoryVC.swift */,
|
|
||||||
30A87A532FEE50B10095E7C6 /* ScheduleHistoryVM.swift */,
|
|
||||||
30A87A4F2FEE4E4B0095E7C6 /* ScheduleHistoryView.swift */,
|
|
||||||
);
|
|
||||||
path = ScheduleHistory;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
30A87A552FEE5A130095E7C6 /* ScheduleViewed */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
30A87A562FEE5A350095E7C6 /* ScheduleViewedVC.swift */,
|
|
||||||
30A87A5A2FEE5AC20095E7C6 /* ScheduleViewedVM.swift */,
|
|
||||||
30A87A582FEE5A4C0095E7C6 /* ScheduleViewedView.swift */,
|
|
||||||
);
|
|
||||||
path = ScheduleViewed;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
30A87A5C2FEE711C0095E7C6 /* Bubble */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
30A87A5F2FEE71B80095E7C6 /* CreateBubbleVC.swift */,
|
|
||||||
30A87A5D2FEE71A50095E7C6 /* CreateBubbleView.swift */,
|
|
||||||
30A87A612FEE724D0095E7C6 /* CreateBubbleTiemView.swift */,
|
|
||||||
30A87A632FEE75520095E7C6 /* CreateBubbleTipsView.swift */,
|
|
||||||
30A87A652FEE843E0095E7C6 /* CreateBubbleDoneView.swift */,
|
|
||||||
30A87A672FEE86560095E7C6 /* CreateBubblePopView.swift */,
|
|
||||||
);
|
|
||||||
path = Bubble;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
30A87A692FEF59E60095E7C6 /* SearchLocation */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
30A87A6C2FEF5BA10095E7C6 /* SearchLocationVC.swift */,
|
|
||||||
30A87A6A2FEF5B950095E7C6 /* SearchLocationView.swift */,
|
|
||||||
30A87A6E2FEF7BE40095E7C6 /* SearchLocationResultVC.swift */,
|
|
||||||
30A87A702FEF7BED0095E7C6 /* SearchLocationResultView.swift */,
|
|
||||||
30EBF71F2FEFD6F2009A8A87 /* GroupChooseView.swift */,
|
|
||||||
);
|
|
||||||
path = SearchLocation;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
30BAB84B2FCD2FA400C33B5C /* InviteJoin */ = {
|
30BAB84B2FCD2FA400C33B5C /* InviteJoin */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -1396,9 +1302,6 @@
|
||||||
30D74ABB2FEA67CE0050EB2C /* CreateSchedule */,
|
30D74ABB2FEA67CE0050EB2C /* CreateSchedule */,
|
||||||
30D74BF22FEB6F5B0050EB2C /* LocationPicker */,
|
30D74BF22FEB6F5B0050EB2C /* LocationPicker */,
|
||||||
30BF300A2FED09A300D9CB52 /* ScheduleDetail */,
|
30BF300A2FED09A300D9CB52 /* ScheduleDetail */,
|
||||||
30A87A492FEE1CB20095E7C6 /* ItineraryDetail */,
|
|
||||||
30A87A4E2FEE4E390095E7C6 /* ScheduleHistory */,
|
|
||||||
30A87A552FEE5A130095E7C6 /* ScheduleViewed */,
|
|
||||||
);
|
);
|
||||||
path = Schedule;
|
path = Schedule;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1722,7 +1625,6 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
30A87A712FEF7BED0095E7C6 /* SearchLocationResultView.swift in Sources */,
|
|
||||||
305A76882FCA8C7000227D26 /* MoyaProvider+Rx.swift in Sources */,
|
305A76882FCA8C7000227D26 /* MoyaProvider+Rx.swift in Sources */,
|
||||||
305A76892FCA8C7000227D26 /* Observable+Response.swift in Sources */,
|
305A76892FCA8C7000227D26 /* Observable+Response.swift in Sources */,
|
||||||
305A768A2FCA8C7000227D26 /* Single+Response.swift in Sources */,
|
305A768A2FCA8C7000227D26 /* Single+Response.swift in Sources */,
|
||||||
|
|
@ -1742,8 +1644,6 @@
|
||||||
305A76922FCA8C7000227D26 /* LogUtils.swift in Sources */,
|
305A76922FCA8C7000227D26 /* LogUtils.swift in Sources */,
|
||||||
305A76932FCA8C7000227D26 /* AddImageCell.swift in Sources */,
|
305A76932FCA8C7000227D26 /* AddImageCell.swift in Sources */,
|
||||||
30EFF3D82FDA8F1000EB35D4 /* EmergencyContactVC.swift in Sources */,
|
30EFF3D82FDA8F1000EB35D4 /* EmergencyContactVC.swift in Sources */,
|
||||||
30A87A682FEE86560095E7C6 /* CreateBubblePopView.swift in Sources */,
|
|
||||||
30A87A4B2FEE1DAB0095E7C6 /* ItineraryDetailVC.swift in Sources */,
|
|
||||||
30DC185E2FD1211D0041DCD1 /* VipRightsVC.swift in Sources */,
|
30DC185E2FD1211D0041DCD1 /* VipRightsVC.swift in Sources */,
|
||||||
305A76942FCA8C7000227D26 /* UploadImageCell.swift in Sources */,
|
305A76942FCA8C7000227D26 /* UploadImageCell.swift in Sources */,
|
||||||
305A76952FCA8C7000227D26 /* CornerRadiusCell.swift in Sources */,
|
305A76952FCA8C7000227D26 /* CornerRadiusCell.swift in Sources */,
|
||||||
|
|
@ -1760,9 +1660,7 @@
|
||||||
3062E8BE2FCEBD0E00CEF511 /* GroupIconListVC.swift in Sources */,
|
3062E8BE2FCEBD0E00CEF511 /* GroupIconListVC.swift in Sources */,
|
||||||
30EFF3DA2FDA935D00EB35D4 /* EmergencyContactFooterView.swift in Sources */,
|
30EFF3DA2FDA935D00EB35D4 /* EmergencyContactFooterView.swift in Sources */,
|
||||||
3062E8C22FCFB86800CEF511 /* CreateGroupViewModel.swift in Sources */,
|
3062E8C22FCFB86800CEF511 /* CreateGroupViewModel.swift in Sources */,
|
||||||
30A87A662FEE843E0095E7C6 /* CreateBubbleDoneView.swift in Sources */,
|
|
||||||
305A769B2FCA8C7000227D26 /* PopupAnimators.swift in Sources */,
|
305A769B2FCA8C7000227D26 /* PopupAnimators.swift in Sources */,
|
||||||
30A87A4D2FEE1DB40095E7C6 /* ItineraryDetailView.swift in Sources */,
|
|
||||||
305A769C2FCA8C7000227D26 /* PopupViewController.swift in Sources */,
|
305A769C2FCA8C7000227D26 /* PopupViewController.swift in Sources */,
|
||||||
305A769D2FCA8C7000227D26 /* PopupViewController+Extension.swift in Sources */,
|
305A769D2FCA8C7000227D26 /* PopupViewController+Extension.swift in Sources */,
|
||||||
30EFF3C42FDA431D00EB35D4 /* ChangePhoneVC.swift in Sources */,
|
30EFF3C42FDA431D00EB35D4 /* ChangePhoneVC.swift in Sources */,
|
||||||
|
|
@ -1779,7 +1677,6 @@
|
||||||
305A76A42FCA8C7000227D26 /* Date+Extension.swift in Sources */,
|
305A76A42FCA8C7000227D26 /* Date+Extension.swift in Sources */,
|
||||||
30D74AAE2FEA13E00050EB2C /* ScheduleView.swift in Sources */,
|
30D74AAE2FEA13E00050EB2C /* ScheduleView.swift in Sources */,
|
||||||
305A76A52FCA8C7000227D26 /* Dictionay+Extension.swift in Sources */,
|
305A76A52FCA8C7000227D26 /* Dictionay+Extension.swift in Sources */,
|
||||||
30A87A522FEE4E5D0095E7C6 /* ScheduleHistoryVC.swift in Sources */,
|
|
||||||
305A76A62FCA8C7000227D26 /* Int+Extension.swift in Sources */,
|
305A76A62FCA8C7000227D26 /* Int+Extension.swift in Sources */,
|
||||||
30EFF3D62FDA8F0100EB35D4 /* EmergencyContactView.swift in Sources */,
|
30EFF3D62FDA8F0100EB35D4 /* EmergencyContactView.swift in Sources */,
|
||||||
30A7A9112FCAEE3D00105780 /* GroupListPopView.swift in Sources */,
|
30A7A9112FCAEE3D00105780 /* GroupListPopView.swift in Sources */,
|
||||||
|
|
@ -1789,7 +1686,6 @@
|
||||||
305A76A92FCA8C7000227D26 /* Optional+Extension.swift in Sources */,
|
305A76A92FCA8C7000227D26 /* Optional+Extension.swift in Sources */,
|
||||||
305A76AA2FCA8C7000227D26 /* Response+ObjectMapper.swift in Sources */,
|
305A76AA2FCA8C7000227D26 /* Response+ObjectMapper.swift in Sources */,
|
||||||
305A76AB2FCA8C7000227D26 /* ScaleType.swift in Sources */,
|
305A76AB2FCA8C7000227D26 /* ScaleType.swift in Sources */,
|
||||||
30A87A572FEE5A350095E7C6 /* ScheduleViewedVC.swift in Sources */,
|
|
||||||
30EFF3E72FDAA93D00EB35D4 /* PrivacyPolicyView.swift in Sources */,
|
30EFF3E72FDAA93D00EB35D4 /* PrivacyPolicyView.swift in Sources */,
|
||||||
305A76AC2FCA8C7000227D26 /* String+Extension.swift in Sources */,
|
305A76AC2FCA8C7000227D26 /* String+Extension.swift in Sources */,
|
||||||
30D74AB82FEA36A50050EB2C /* ItineraryService.swift in Sources */,
|
30D74AB82FEA36A50050EB2C /* ItineraryService.swift in Sources */,
|
||||||
|
|
@ -1801,7 +1697,6 @@
|
||||||
305A76B12FCA8C7000227D26 /* UIImage+Extension.swift in Sources */,
|
305A76B12FCA8C7000227D26 /* UIImage+Extension.swift in Sources */,
|
||||||
305A76B22FCA8C7000227D26 /* UIImage+Resource.swift in Sources */,
|
305A76B22FCA8C7000227D26 /* UIImage+Resource.swift in Sources */,
|
||||||
305A76B32FCA8C7000227D26 /* UILabel+Extension.swift in Sources */,
|
305A76B32FCA8C7000227D26 /* UILabel+Extension.swift in Sources */,
|
||||||
30A87A5B2FEE5AC20095E7C6 /* ScheduleViewedVM.swift in Sources */,
|
|
||||||
305A76B42FCA8C7000227D26 /* UINavigationController+FDFullscreenPopGesture.m in Sources */,
|
305A76B42FCA8C7000227D26 /* UINavigationController+FDFullscreenPopGesture.m in Sources */,
|
||||||
305A76B52FCA8C7000227D26 /* UITableView+Extension.swift in Sources */,
|
305A76B52FCA8C7000227D26 /* UITableView+Extension.swift in Sources */,
|
||||||
30CCDE582FE39F8C00F5214A /* SOSView.swift in Sources */,
|
30CCDE582FE39F8C00F5214A /* SOSView.swift in Sources */,
|
||||||
|
|
@ -1820,15 +1715,12 @@
|
||||||
30EFF3C02FD958AE00EB35D4 /* AccountVC.swift in Sources */,
|
30EFF3C02FD958AE00EB35D4 /* AccountVC.swift in Sources */,
|
||||||
30C4C0202FDC0EC5009215C1 /* GroupInfoView.swift in Sources */,
|
30C4C0202FDC0EC5009215C1 /* GroupInfoView.swift in Sources */,
|
||||||
305A76BF2FCA8C7000227D26 /* ListService.swift in Sources */,
|
305A76BF2FCA8C7000227D26 /* ListService.swift in Sources */,
|
||||||
30A87A6D2FEF5BA10095E7C6 /* SearchLocationVC.swift in Sources */,
|
|
||||||
305A76C02FCA8C7000227D26 /* BaseNavigationController.swift in Sources */,
|
305A76C02FCA8C7000227D26 /* BaseNavigationController.swift in Sources */,
|
||||||
305A76C12FCA8C7000227D26 /* BaseViewController.swift in Sources */,
|
305A76C12FCA8C7000227D26 /* BaseViewController.swift in Sources */,
|
||||||
3062E8C02FCED7BB00CEF511 /* GroupIconListView.swift in Sources */,
|
3062E8C02FCED7BB00CEF511 /* GroupIconListView.swift in Sources */,
|
||||||
30BF300C2FED09BA00D9CB52 /* ScheduleDetailView.swift in Sources */,
|
30BF300C2FED09BA00D9CB52 /* ScheduleDetailView.swift in Sources */,
|
||||||
305A76C22FCA8C7000227D26 /* BaseViewModel.swift in Sources */,
|
305A76C22FCA8C7000227D26 /* BaseViewModel.swift in Sources */,
|
||||||
30A87A622FEE724D0095E7C6 /* CreateBubbleTiemView.swift in Sources */,
|
|
||||||
30DC185A2FD11E7A0041DCD1 /* WebOperations.swift in Sources */,
|
30DC185A2FD11E7A0041DCD1 /* WebOperations.swift in Sources */,
|
||||||
30A87A642FEE75520095E7C6 /* CreateBubbleTipsView.swift in Sources */,
|
|
||||||
30DC185B2FD11E7A0041DCD1 /* NavigationTitleView.swift in Sources */,
|
30DC185B2FD11E7A0041DCD1 /* NavigationTitleView.swift in Sources */,
|
||||||
30DC185C2FD11E7A0041DCD1 /* WebViewController.swift in Sources */,
|
30DC185C2FD11E7A0041DCD1 /* WebViewController.swift in Sources */,
|
||||||
305A76C32FCA8C7000227D26 /* MainTabBarController.swift in Sources */,
|
305A76C32FCA8C7000227D26 /* MainTabBarController.swift in Sources */,
|
||||||
|
|
@ -1850,7 +1742,6 @@
|
||||||
305A76CE2FCA8C7000227D26 /* RouterManager.swift in Sources */,
|
305A76CE2FCA8C7000227D26 /* RouterManager.swift in Sources */,
|
||||||
3062E8C72FCFD02F00CEF511 /* VipRechargeView.swift in Sources */,
|
3062E8C72FCFD02F00CEF511 /* VipRechargeView.swift in Sources */,
|
||||||
305A76CF2FCA8C7000227D26 /* CountDownService.swift in Sources */,
|
305A76CF2FCA8C7000227D26 /* CountDownService.swift in Sources */,
|
||||||
30A87A502FEE4E4B0095E7C6 /* ScheduleHistoryView.swift in Sources */,
|
|
||||||
305A76D02FCA8C7000227D26 /* MoneyFormatter.swift in Sources */,
|
305A76D02FCA8C7000227D26 /* MoneyFormatter.swift in Sources */,
|
||||||
305A76D12FCA8C7000227D26 /* TimeSpecificNotificationManager.swift in Sources */,
|
305A76D12FCA8C7000227D26 /* TimeSpecificNotificationManager.swift in Sources */,
|
||||||
305A76D22FCA8C7000227D26 /* ThemeManager.swift in Sources */,
|
305A76D22FCA8C7000227D26 /* ThemeManager.swift in Sources */,
|
||||||
|
|
@ -1864,18 +1755,15 @@
|
||||||
305A76D82FCA8C7000227D26 /* Action.swift in Sources */,
|
305A76D82FCA8C7000227D26 /* Action.swift in Sources */,
|
||||||
30EFF3B02FD8122E00EB35D4 /* GroupTagListView.swift in Sources */,
|
30EFF3B02FD8122E00EB35D4 /* GroupTagListView.swift in Sources */,
|
||||||
305A76D92FCA8C7000227D26 /* Action+Internal.swift in Sources */,
|
305A76D92FCA8C7000227D26 /* Action+Internal.swift in Sources */,
|
||||||
30EBF7202FEFD6F2009A8A87 /* GroupChooseView.swift in Sources */,
|
|
||||||
305A76DA2FCA8C7000227D26 /* Button+Action.swift in Sources */,
|
305A76DA2FCA8C7000227D26 /* Button+Action.swift in Sources */,
|
||||||
305A76DB2FCA8C7000227D26 /* Control+Action.swift in Sources */,
|
305A76DB2FCA8C7000227D26 /* Control+Action.swift in Sources */,
|
||||||
305A76DC2FCA8C7000227D26 /* InputSubject.swift in Sources */,
|
305A76DC2FCA8C7000227D26 /* InputSubject.swift in Sources */,
|
||||||
305A76DD2FCA8C7000227D26 /* NSObject+Rx.swift in Sources */,
|
305A76DD2FCA8C7000227D26 /* NSObject+Rx.swift in Sources */,
|
||||||
305A79902FCAC61A00227D26 /* InviteMemberVC.swift in Sources */,
|
305A79902FCAC61A00227D26 /* InviteMemberVC.swift in Sources */,
|
||||||
30A87A542FEE50B10095E7C6 /* ScheduleHistoryVM.swift in Sources */,
|
|
||||||
305A76DE2FCA8C7000227D26 /* ObservableType+ObjectMapper.swift in Sources */,
|
305A76DE2FCA8C7000227D26 /* ObservableType+ObjectMapper.swift in Sources */,
|
||||||
305A76DF2FCA8C7000227D26 /* Single+ObjectMapper.swift in Sources */,
|
305A76DF2FCA8C7000227D26 /* Single+ObjectMapper.swift in Sources */,
|
||||||
30DC18602FD12A020041DCD1 /* VipWaivePopView.swift in Sources */,
|
30DC18602FD12A020041DCD1 /* VipWaivePopView.swift in Sources */,
|
||||||
30D74AAB2FE8C7700050EB2C /* GPSSignalHelper.swift in Sources */,
|
30D74AAB2FE8C7700050EB2C /* GPSSignalHelper.swift in Sources */,
|
||||||
30A87A6B2FEF5B950095E7C6 /* SearchLocationView.swift in Sources */,
|
|
||||||
305A76E02FCA8C7000227D26 /* GroupView.swift in Sources */,
|
305A76E02FCA8C7000227D26 /* GroupView.swift in Sources */,
|
||||||
30EFF3BB2FD90D7600EB35D4 /* ConfirmPopVC.swift in Sources */,
|
30EFF3BB2FD90D7600EB35D4 /* ConfirmPopVC.swift in Sources */,
|
||||||
30BAB8512FCD331C00C33B5C /* GroupAPI.swift in Sources */,
|
30BAB8512FCD331C00C33B5C /* GroupAPI.swift in Sources */,
|
||||||
|
|
@ -1883,7 +1771,6 @@
|
||||||
30D74AB22FEA1D5D0050EB2C /* ScheduleViewModel.swift in Sources */,
|
30D74AB22FEA1D5D0050EB2C /* ScheduleViewModel.swift in Sources */,
|
||||||
30EFF3B52FD8F1D000EB35D4 /* ReviewMemberListVC.swift in Sources */,
|
30EFF3B52FD8F1D000EB35D4 /* ReviewMemberListVC.swift in Sources */,
|
||||||
30BAB84D2FCD2FDE00C33B5C /* InviteJoinView.swift in Sources */,
|
30BAB84D2FCD2FDE00C33B5C /* InviteJoinView.swift in Sources */,
|
||||||
30A87A592FEE5A4C0095E7C6 /* ScheduleViewedView.swift in Sources */,
|
|
||||||
30EFF3CF2FDA669800EB35D4 /* MyProfileVC.swift in Sources */,
|
30EFF3CF2FDA669800EB35D4 /* MyProfileVC.swift in Sources */,
|
||||||
30CCDE5C2FE3A1A800F5214A /* SOSPracticeView.swift in Sources */,
|
30CCDE5C2FE3A1A800F5214A /* SOSPracticeView.swift in Sources */,
|
||||||
30EFF3E52FDAA93400EB35D4 /* PrivacyPolicyVC.swift in Sources */,
|
30EFF3E52FDAA93400EB35D4 /* PrivacyPolicyVC.swift in Sources */,
|
||||||
|
|
@ -1907,17 +1794,14 @@
|
||||||
305A76EA2FCA8C7000227D26 /* OneTapLoginView.swift in Sources */,
|
305A76EA2FCA8C7000227D26 /* OneTapLoginView.swift in Sources */,
|
||||||
305A76EB2FCA8C7000227D26 /* CircleMember.swift in Sources */,
|
305A76EB2FCA8C7000227D26 /* CircleMember.swift in Sources */,
|
||||||
30CCDE5A2FE39F9D00F5214A /* SOSViewController.swift in Sources */,
|
30CCDE5A2FE39F9D00F5214A /* SOSViewController.swift in Sources */,
|
||||||
30A87A6F2FEF7BE40095E7C6 /* SearchLocationResultVC.swift in Sources */,
|
|
||||||
30D74AB42FEA25B90050EB2C /* ViewedModel.swift in Sources */,
|
30D74AB42FEA25B90050EB2C /* ViewedModel.swift in Sources */,
|
||||||
305A76EC2FCA8C7000227D26 /* MemberAnnotation.swift in Sources */,
|
305A76EC2FCA8C7000227D26 /* MemberAnnotation.swift in Sources */,
|
||||||
305A76ED2FCA8C7000227D26 /* MemberAnnotationView.swift in Sources */,
|
305A76ED2FCA8C7000227D26 /* MemberAnnotationView.swift in Sources */,
|
||||||
30BAB8532FCD337C00C33B5C /* GroupService.swift in Sources */,
|
30BAB8532FCD337C00C33B5C /* GroupService.swift in Sources */,
|
||||||
305A76EE2FCA8C7000227D26 /* MineView.swift in Sources */,
|
305A76EE2FCA8C7000227D26 /* MineView.swift in Sources */,
|
||||||
305A76EF2FCA8C7000227D26 /* MineViewController.swift in Sources */,
|
305A76EF2FCA8C7000227D26 /* MineViewController.swift in Sources */,
|
||||||
30A87A5E2FEE71A50095E7C6 /* CreateBubbleView.swift in Sources */,
|
|
||||||
30D74ABF2FEA67F30050EB2C /* CreateScheduleView.swift in Sources */,
|
30D74ABF2FEA67F30050EB2C /* CreateScheduleView.swift in Sources */,
|
||||||
305A76F02FCA8C7000227D26 /* MineViewModel.swift in Sources */,
|
305A76F02FCA8C7000227D26 /* MineViewModel.swift in Sources */,
|
||||||
30A87A602FEE71B80095E7C6 /* CreateBubbleVC.swift in Sources */,
|
|
||||||
305A76F12FCA8C7000227D26 /* SystemService.swift in Sources */,
|
305A76F12FCA8C7000227D26 /* SystemService.swift in Sources */,
|
||||||
30DC18522FD009CD0041DCD1 /* VipExpenseModel.swift in Sources */,
|
30DC18522FD009CD0041DCD1 /* VipExpenseModel.swift in Sources */,
|
||||||
30C4C01B2FDBF09D009215C1 /* RemoveMemberView.swift in Sources */,
|
30C4C01B2FDBF09D009215C1 /* RemoveMemberView.swift in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,6 @@ enum ItineraryAPI {
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - id: 行程ID
|
/// - id: 行程ID
|
||||||
case delete(id: String)
|
case delete(id: String)
|
||||||
|
|
||||||
/// 关注行程
|
|
||||||
/// - Parameters:
|
|
||||||
/// - id: 行程ID
|
|
||||||
/// - op: 1关注 2取消关注
|
|
||||||
case follow(id: String, op: Int)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ItineraryAPI: MultiTargetProtocol {
|
extension ItineraryAPI: MultiTargetProtocol {
|
||||||
|
|
@ -56,8 +50,6 @@ extension ItineraryAPI: MultiTargetProtocol {
|
||||||
return "mapi/itinerary/route/set"
|
return "mapi/itinerary/route/set"
|
||||||
case .delete:
|
case .delete:
|
||||||
return "mapi/itinerary/route/delete"
|
return "mapi/itinerary/route/delete"
|
||||||
case .follow:
|
|
||||||
return "mapi/itinerary/route/follow"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,9 +71,7 @@ extension ItineraryAPI: MultiTargetProtocol {
|
||||||
params["follow"] = follow
|
params["follow"] = follow
|
||||||
params["own"] = own
|
params["own"] = own
|
||||||
params["history"] = history
|
params["history"] = history
|
||||||
if !group_key.isEmpty {
|
|
||||||
params["group_key"] = group_key
|
params["group_key"] = group_key
|
||||||
}
|
|
||||||
params["page"] = page
|
params["page"] = page
|
||||||
params["limit"] = 20
|
params["limit"] = 20
|
||||||
return .requestParameters(parameters: params, encoding: URLEncoding())
|
return .requestParameters(parameters: params, encoding: URLEncoding())
|
||||||
|
|
@ -108,13 +98,6 @@ extension ItineraryAPI: MultiTargetProtocol {
|
||||||
var params = Parameters()
|
var params = Parameters()
|
||||||
params["id"] = id
|
params["id"] = id
|
||||||
return .requestParameters(parameters: params, encoding: URLEncoding())
|
return .requestParameters(parameters: params, encoding: URLEncoding())
|
||||||
|
|
||||||
case let .follow(id, op):
|
|
||||||
var params = Parameters()
|
|
||||||
params["id"] = id
|
|
||||||
params["op"] = op
|
|
||||||
return .requestParameters(parameters: params, encoding: JSONEncoding())
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,6 @@ enum SystemAPI {
|
||||||
|
|
||||||
/// SOS
|
/// SOS
|
||||||
case sos(enable: Bool)
|
case sos(enable: Bool)
|
||||||
|
|
||||||
/// 查找
|
|
||||||
/// - Parameters:
|
|
||||||
/// - op_type: plate_num 车牌
|
|
||||||
/// - phone 手机号
|
|
||||||
case search(op_type: String, number: String)
|
|
||||||
|
|
||||||
/// 手机归属地
|
|
||||||
/// - Parameters:
|
|
||||||
/// - phone 手机号
|
|
||||||
case phoneArea(phone: String)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SystemAPI: MultiTargetProtocol {
|
extension SystemAPI: MultiTargetProtocol {
|
||||||
|
|
@ -49,18 +37,14 @@ extension SystemAPI: MultiTargetProtocol {
|
||||||
return "api/weixin/service"
|
return "api/weixin/service"
|
||||||
case .sos:
|
case .sos:
|
||||||
return "mapi/sos/operate"
|
return "mapi/sos/operate"
|
||||||
case .search:
|
|
||||||
return "mapi/car/search"
|
|
||||||
case .phoneArea:
|
|
||||||
return "mapi/phonearea"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var method: Moya.Method {
|
var method: Moya.Method {
|
||||||
switch self {
|
switch self {
|
||||||
case .userConfig, .wechatService, .phoneArea:
|
case .userConfig, .wechatService:
|
||||||
return .get
|
return .get
|
||||||
default:
|
case .sendCode, .sos:
|
||||||
return .post
|
return .post
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -83,16 +67,6 @@ extension SystemAPI: MultiTargetProtocol {
|
||||||
params["enable"] = enable
|
params["enable"] = enable
|
||||||
return .requestParameters(parameters: params, encoding: JSONEncoding())
|
return .requestParameters(parameters: params, encoding: JSONEncoding())
|
||||||
|
|
||||||
case let .search(op_type, number):
|
|
||||||
var params = Parameters()
|
|
||||||
params["op_type"] = op_type
|
|
||||||
params["number"] = number
|
|
||||||
return .requestParameters(parameters: params, encoding: JSONEncoding())
|
|
||||||
|
|
||||||
case let .phoneArea(phone):
|
|
||||||
var params = Parameters()
|
|
||||||
params["phone"] = phone
|
|
||||||
return .requestParameters(parameters: params, encoding: URLEncoding())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,12 +66,6 @@ enum UserAPI {
|
||||||
|
|
||||||
/// 查看关注用户列表
|
/// 查看关注用户列表
|
||||||
case followList
|
case followList
|
||||||
|
|
||||||
/// 设置气泡
|
|
||||||
/// - Parameters:
|
|
||||||
/// - enable:
|
|
||||||
/// - keep_time:
|
|
||||||
case bubble(enable: Bool, keep_time: Int)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UserAPI: MultiTargetProtocol {
|
extension UserAPI: MultiTargetProtocol {
|
||||||
|
|
@ -106,8 +100,6 @@ extension UserAPI: MultiTargetProtocol {
|
||||||
return "api/user/notice"
|
return "api/user/notice"
|
||||||
case .followList:
|
case .followList:
|
||||||
return "mapi/user/followed"
|
return "mapi/user/followed"
|
||||||
case .bubble:
|
|
||||||
return "mapi/bubble/operate"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,14 +183,6 @@ extension UserAPI: MultiTargetProtocol {
|
||||||
|
|
||||||
case .followList:
|
case .followList:
|
||||||
return .requestParameters(parameters: Parameters(), encoding: URLEncoding())
|
return .requestParameters(parameters: Parameters(), encoding: URLEncoding())
|
||||||
|
|
||||||
case let .bubble(enable, keep_time):
|
|
||||||
var params = Parameters()
|
|
||||||
params["enable"] = enable
|
|
||||||
if keep_time != -1 {
|
|
||||||
params["keep_time"] = keep_time
|
|
||||||
}
|
|
||||||
return .requestParameters(parameters: params, encoding: JSONEncoding())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
},
|
|
||||||
"properties" : {
|
|
||||||
"provides-namespace" : true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "text@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "text@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Vector@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Vector@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "upgrade_bg@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "upgrade_bg@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 121 KiB |
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "vip_pop@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "vip_pop@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 371 KiB |
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "schedule@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "schedule@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Rectangle_42232@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Rectangle_42232@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 504 B |
|
Before Width: | Height: | Size: 596 B |
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Group_2303@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Group_2303@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 703 B |
|
Before Width: | Height: | Size: 844 B |
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
},
|
|
||||||
"properties" : {
|
|
||||||
"provides-namespace" : true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Group_1554@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Group_1554@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 154 KiB |
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Group_1677@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Group_1677@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Group_686@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Group_686@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Group_687@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "Group_687@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "mask@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "mask@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "result_bg@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "result_bg@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
|
@ -31,8 +31,6 @@ extension Notification.Name {
|
||||||
static let RefreshGroupInfoNotification = Notification.Name("RefreshGroupInfoNotification")
|
static let RefreshGroupInfoNotification = Notification.Name("RefreshGroupInfoNotification")
|
||||||
/// 刷新IM圈子列表数据
|
/// 刷新IM圈子列表数据
|
||||||
static let RefreshIMGroupListNotification = Notification.Name("RefreshIMGroupListNotification")
|
static let RefreshIMGroupListNotification = Notification.Name("RefreshIMGroupListNotification")
|
||||||
/// 查看成员位置
|
|
||||||
static let ShowMemberLocationNotification = Notification.Name("ShowMemberLocationNotification")
|
|
||||||
/// 支付宝/微信支付结果回调
|
/// 支付宝/微信支付结果回调
|
||||||
static let RequestOrderPayStatusNotification = Notification.Name("RequestOrderPayStatusNotification")
|
static let RequestOrderPayStatusNotification = Notification.Name("RequestOrderPayStatusNotification")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -512,10 +512,3 @@ extension String {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension String {
|
|
||||||
var isPhoneNumber: Bool {
|
|
||||||
let reg = "^1[3-9]\\d{9}$"
|
|
||||||
return NSPredicate(format: "SELF MATCHES %@", reg).evaluate(with: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -59,12 +59,6 @@ class BaseViewController: UIViewController {
|
||||||
(tabBarController as? MainTabBarController)?.updateTabBarVisibility()
|
(tabBarController as? MainTabBarController)?.updateTabBarVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
|
||||||
super.viewDidAppear(animated)
|
|
||||||
// popToRoot 动画完成后 navigation stack 才减到 1,此时再恢复 tabBar
|
|
||||||
(tabBarController as? MainTabBarController)?.updateTabBarVisibility()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
super.viewWillDisappear(animated)
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ final class MainTabBarController: UITabBarController {
|
||||||
customTabBar.delegate = self
|
customTabBar.delegate = self
|
||||||
// 点击"探索"(index=1)时游客跳登录、不选中
|
// 点击"探索"(index=1)时游客跳登录、不选中
|
||||||
customTabBar.shouldSelectTab = { index in
|
customTabBar.shouldSelectTab = { index in
|
||||||
if index == 1 || index == 2, AppContextManager.shared.isGuest {
|
if index == 1, AppContextManager.shared.isGuest {
|
||||||
AppRouter.push(Route.login)
|
AppRouter.push(Route.login)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +98,7 @@ final class MainTabBarController: UITabBarController {
|
||||||
extension MainTabBarController: QuickLocationTabBarDelegate {
|
extension MainTabBarController: QuickLocationTabBarDelegate {
|
||||||
func tabBar(_ tabBar: QuickLocationTabBar, didSelectTabAt index: Int) {
|
func tabBar(_ tabBar: QuickLocationTabBar, didSelectTabAt index: Int) {
|
||||||
// 点击"探索"(index=1)时判断游客,是则跳登录、不选中
|
// 点击"探索"(index=1)时判断游客,是则跳登录、不选中
|
||||||
if index == 1 || index == 2, AppContextManager.shared.isGuest {
|
if index == 1, AppContextManager.shared.isGuest {
|
||||||
AppRouter.push(Route.login)
|
AppRouter.push(Route.login)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,18 +57,6 @@ enum Route: String {
|
||||||
case createSchedule = "createSchedule"
|
case createSchedule = "createSchedule"
|
||||||
/// 行程详情
|
/// 行程详情
|
||||||
case scheduleDetail = "scheduleDetail"
|
case scheduleDetail = "scheduleDetail"
|
||||||
/// 行程路线
|
|
||||||
case itineraryDetail = "itineraryDetail"
|
|
||||||
/// 历史行程
|
|
||||||
case scheduleHistory = "scheduleHistory"
|
|
||||||
/// 看过我
|
|
||||||
case scheduleViewed = "scheduleViewed"
|
|
||||||
/// 创建气泡
|
|
||||||
case createBubble = "createBubble"
|
|
||||||
/// 查找位置
|
|
||||||
case searchLocation = "searchLocation"
|
|
||||||
/// 查找位置结果
|
|
||||||
case searchLocationResult = "searchLocationResult"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Route: RouterTarget {
|
extension Route: RouterTarget {
|
||||||
|
|
@ -287,42 +275,6 @@ extension AppRouter: AppRouterProtocol {
|
||||||
return ScheduleDetailVC(routeId: routeId,
|
return ScheduleDetailVC(routeId: routeId,
|
||||||
scheduleJson: parameters["scheduleJson"].safeDictionary as! [String : Any])
|
scheduleJson: parameters["scheduleJson"].safeDictionary as! [String : Any])
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 行程路线
|
|
||||||
AppRouter.register(Route.itineraryDetail) { url, parameters in
|
|
||||||
return ItineraryDetailVC(scheduleJson: parameters["scheduleJson"].safeDictionary as! [String : Any])
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 历史行程
|
|
||||||
AppRouter.register(Route.scheduleHistory) { url, parameters in
|
|
||||||
ScheduleHistoryVC()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 谁看过我
|
|
||||||
AppRouter.register(Route.scheduleViewed) { url, parameters in
|
|
||||||
ScheduleViewedVC()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 创建气泡
|
|
||||||
AppRouter.register(Route.createBubble) { url, parameters in
|
|
||||||
let vc = CreateBubbleVC()
|
|
||||||
vc.isNeedLogin = true
|
|
||||||
return vc
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 查找位置
|
|
||||||
AppRouter.register(Route.searchLocation) { url, parameters in
|
|
||||||
let vc = SearchLocationVC()
|
|
||||||
// vc.isNeedLogin = true
|
|
||||||
return vc
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 查找位置结果
|
|
||||||
AppRouter.register(Route.searchLocationResult) { url, parameters in
|
|
||||||
SearchLocationResultVC(phone: parameters["phone"].safeString,
|
|
||||||
code: parameters["code"].safeInt,
|
|
||||||
memberData: parameters["memberData"].safeDictionary as! [String : Any])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,187 +0,0 @@
|
||||||
//
|
|
||||||
// CreateBubbleDoneView.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
|
|
||||||
class CreateBubbleDoneView: UIView {
|
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
|
||||||
|
|
||||||
private var countdownTimer: Timer?
|
|
||||||
private var endDate: Date = Date()
|
|
||||||
|
|
||||||
func startCountdown(endDate: Date) {
|
|
||||||
self.endDate = endDate
|
|
||||||
countdownTimer?.invalidate()
|
|
||||||
countdownTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
|
|
||||||
self?.updateTime()
|
|
||||||
}
|
|
||||||
updateTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateTime() {
|
|
||||||
let remaining = endDate.timeIntervalSince(Date())
|
|
||||||
if remaining <= 0 {
|
|
||||||
timeLab.text = "00:00:00"
|
|
||||||
countdownTimer?.invalidate()
|
|
||||||
countdownTimer = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let hours = Int(remaining) / 3600
|
|
||||||
let minutes = (Int(remaining) % 3600) / 60
|
|
||||||
let seconds = Int(remaining) % 60
|
|
||||||
timeLab.text = String(format: "%02d:%02d:%02d", hours, minutes, seconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupRx() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupUI() {
|
|
||||||
addSubview(titleLab)
|
|
||||||
addSubview(timeIcon)
|
|
||||||
addSubview(timeLab)
|
|
||||||
addSubview(messageView)
|
|
||||||
addSubview(iconView)
|
|
||||||
addSubview(scrollView)
|
|
||||||
addSubview(popupBtn)
|
|
||||||
|
|
||||||
titleLab.layoutChain
|
|
||||||
.top(15)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
timeLab.layoutChain
|
|
||||||
.topToBottomOfView(titleLab, offset: 12)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
timeIcon.layoutChain
|
|
||||||
.rightToLeftOfView(timeLab, offset: -8)
|
|
||||||
.centerY(timeLab)
|
|
||||||
|
|
||||||
messageView.layoutChain
|
|
||||||
.topToBottomOfView(timeLab, offset: 25)
|
|
||||||
.left(79)
|
|
||||||
.right(30)
|
|
||||||
|
|
||||||
iconView.layoutChain
|
|
||||||
.rightToLeftOfView(messageView, offset: -9)
|
|
||||||
.bottomToView(messageView)
|
|
||||||
.height(40)
|
|
||||||
.width(40)
|
|
||||||
|
|
||||||
popupBtn.layoutChain
|
|
||||||
.width(240)
|
|
||||||
.height(50)
|
|
||||||
.centerX()
|
|
||||||
.bottom(kSafeBottomMargin + 20)
|
|
||||||
|
|
||||||
scrollView.layoutChain
|
|
||||||
.topToBottomOfView(messageView, offset: 20)
|
|
||||||
.edgesHorzontal()
|
|
||||||
.bottomToTopOfView(popupBtn, offset: -20)
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var titleLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = "您的泡将在以下时间弹出:"
|
|
||||||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
label.numberOfLines = 0
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var popupBtn: 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
|
|
||||||
return btn
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var timeIcon: UIImageView = {
|
|
||||||
let view = UIImageView()
|
|
||||||
view.image = UIImage(named: "Bubble/time")
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var timeLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.font = .systemFont(ofSize: 20, weight: .bold)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var iconView: UIImageView = {
|
|
||||||
let view = UIImageView(image: UIImage(named: "UserIcon/7"))
|
|
||||||
view.cornerRadius = 20
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var messageView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = UIColor(hexStr: "#EBF6F9")
|
|
||||||
view.cornerRadius = 10
|
|
||||||
|
|
||||||
view.addSubview(messageLab)
|
|
||||||
messageLab.layoutChain
|
|
||||||
.edgesHorzontal(18)
|
|
||||||
.edgesVertical(15)
|
|
||||||
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var messageLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
label.numberOfLines = 0
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var scrollView: UIScrollView = {
|
|
||||||
let view = UIScrollView()
|
|
||||||
view.backgroundColor = .clear
|
|
||||||
view.showsVerticalScrollIndicator = false
|
|
||||||
view.bounces = false
|
|
||||||
|
|
||||||
let contentView = UIView()
|
|
||||||
contentView.backgroundColor = .clear
|
|
||||||
view.addSubview(contentView)
|
|
||||||
contentView.layoutChain.edges().widthToView(view)
|
|
||||||
|
|
||||||
let textImgView = UIImageView()
|
|
||||||
textImgView.image = UIImage(named: "Bubble/text")
|
|
||||||
contentView.addSubview(textImgView)
|
|
||||||
textImgView.layoutChain
|
|
||||||
.edgesVertical()
|
|
||||||
.edgesHorzontal(30)
|
|
||||||
.heightToWidth(1450/630)
|
|
||||||
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: .zero)
|
|
||||||
self.isHidden = true
|
|
||||||
backgroundColor = .white
|
|
||||||
setupUI()
|
|
||||||
setupRx()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
countdownTimer?.invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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.vipRecharge)
|
|
||||||
}).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")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
//
|
|
||||||
// CreateBubbleTiemView.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import RxGesture
|
|
||||||
|
|
||||||
class CreateBubbleTiemView: UIView {
|
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
|
||||||
let selectedHour = BehaviorRelay<Int>(value: 1)
|
|
||||||
var onNextTap: (() -> Void)?
|
|
||||||
private let hours = Array(1...6)
|
|
||||||
|
|
||||||
private func setupRx() {
|
|
||||||
nextBtn.rx.tap.subscribe(onNext: { [weak self] _ in
|
|
||||||
self?.onNextTap?()
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupUI() {
|
|
||||||
addSubview(titleLab)
|
|
||||||
addSubview(pickerView)
|
|
||||||
addSubview(hourLab)
|
|
||||||
addSubview(nextBtn)
|
|
||||||
|
|
||||||
titleLab.layoutChain
|
|
||||||
.top(15)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
pickerView.layoutChain
|
|
||||||
.centerX().centerY()
|
|
||||||
.edgesHorzontal(15)
|
|
||||||
.height(180)
|
|
||||||
|
|
||||||
hourLab.layoutChain
|
|
||||||
.centerX(self, offset: 50)
|
|
||||||
.centerY(pickerView)
|
|
||||||
|
|
||||||
nextBtn.layoutChain
|
|
||||||
.centerX()
|
|
||||||
.width(240)
|
|
||||||
.height(50)
|
|
||||||
.bottom(kSafeBottomMargin + 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var titleLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = "您会在这个气泡里待多久?"
|
|
||||||
label.font = .systemFont(ofSize: 20, weight: .bold)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var pickerView: UIPickerView = {
|
|
||||||
let pv = UIPickerView()
|
|
||||||
pv.delegate = self
|
|
||||||
pv.dataSource = self
|
|
||||||
return pv
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var hourLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = "小时"
|
|
||||||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
|
||||||
label.textColor = UIColor(hexStr: "#16B3FF")
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var nextBtn: 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
|
|
||||||
return btn
|
|
||||||
}()
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: .zero)
|
|
||||||
self.isHidden = true
|
|
||||||
backgroundColor = .white
|
|
||||||
setupUI()
|
|
||||||
setupRx()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CreateBubbleTiemView: UIPickerViewDataSource, UIPickerViewDelegate {
|
|
||||||
func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
|
||||||
hours.count
|
|
||||||
}
|
|
||||||
|
|
||||||
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
|
|
||||||
let label = view as? UILabel ?? UILabel()
|
|
||||||
label.textAlignment = .center
|
|
||||||
label.font = .systemFont(ofSize: 20, weight: .medium)
|
|
||||||
let text = "\(hours[row])"
|
|
||||||
let selected = row == pickerView.selectedRow(inComponent: component)
|
|
||||||
label.textColor = selected ? UIColor(hexStr: "#16B3FF") : UIColor(hexStr: "#999999")
|
|
||||||
label.text = text
|
|
||||||
return label
|
|
||||||
}
|
|
||||||
|
|
||||||
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
|
|
||||||
selectedHour.accept(hours[row])
|
|
||||||
pickerView.reloadAllComponents()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
//
|
|
||||||
// CreateBubbleTipsView.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
|
|
||||||
class CreateBubbleTipsView: UIView {
|
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
|
||||||
var messageText: String = ""
|
|
||||||
|
|
||||||
private func setupRx() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupUI() {
|
|
||||||
addSubview(titleLab)
|
|
||||||
addSubview(tipsLab)
|
|
||||||
addSubview(messageView)
|
|
||||||
addSubview(iconView)
|
|
||||||
addSubview(doneBtn)
|
|
||||||
|
|
||||||
titleLab.layoutChain
|
|
||||||
.top(15)
|
|
||||||
.edgesHorzontal(58)
|
|
||||||
|
|
||||||
messageView.layoutChain
|
|
||||||
.topToBottomOfView(titleLab, offset: 47)
|
|
||||||
.left(79)
|
|
||||||
.right(30)
|
|
||||||
|
|
||||||
iconView.layoutChain
|
|
||||||
.rightToLeftOfView(messageView, offset: -9)
|
|
||||||
.bottomToView(messageView)
|
|
||||||
.height(40)
|
|
||||||
.width(40)
|
|
||||||
|
|
||||||
doneBtn.layoutChain
|
|
||||||
.centerX()
|
|
||||||
.width(240)
|
|
||||||
.height(50)
|
|
||||||
.bottom(kSafeBottomMargin + 10)
|
|
||||||
|
|
||||||
tipsLab.layoutChain
|
|
||||||
.edgesHorzontal(38)
|
|
||||||
.bottomToTopOfView(doneBtn, offset: -20)
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var titleLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = "我们让您的圈子知道您创建了一个带有以下消息的气泡"
|
|
||||||
label.font = .systemFont(ofSize: 20, weight: .bold)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
label.numberOfLines = 0
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var iconView: UIImageView = {
|
|
||||||
let view = UIImageView(image: UIImage(named: "UserIcon/7"))
|
|
||||||
view.cornerRadius = 20
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var messageView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = UIColor(hexStr: "#EBF6F9")
|
|
||||||
view.cornerRadius = 10
|
|
||||||
|
|
||||||
view.addSubview(messageLab)
|
|
||||||
messageLab.layoutChain
|
|
||||||
.edgesHorzontal(18)
|
|
||||||
.edgesVertical(15)
|
|
||||||
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var messageLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
label.numberOfLines = 0
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var tipsLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = "您的圈子成员只会看到您在气泡中。当您在气泡中,将不会共享您的确切位置。"
|
|
||||||
label.font = .systemFont(ofSize: 14, weight: .medium)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
label.numberOfLines = 0
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var doneBtn: 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
|
|
||||||
return btn
|
|
||||||
}()
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: .zero)
|
|
||||||
self.isHidden = true
|
|
||||||
backgroundColor = .white
|
|
||||||
setupUI()
|
|
||||||
setupRx()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
//
|
|
||||||
// CreateBubbleVC.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
|
|
||||||
class CreateBubbleVC: BaseViewController {
|
|
||||||
|
|
||||||
fileprivate var rootView: CreateBubbleView!
|
|
||||||
|
|
||||||
override func loadView() {
|
|
||||||
rootView = CreateBubbleView(frame: UIScreen.main.bounds)
|
|
||||||
view = rootView
|
|
||||||
}
|
|
||||||
|
|
||||||
private var doneBtn: UIButton {
|
|
||||||
rootView.createBubbleTipsView.doneBtn
|
|
||||||
}
|
|
||||||
|
|
||||||
private var popupBtn: UIButton {
|
|
||||||
rootView.createBubbleDoneView.popupBtn
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
doneBtn.rx.tap.subscribe(onNext: { [weak self] in
|
|
||||||
guard let self = self else { return }
|
|
||||||
if AppContextManager.shared.vip > 1 {
|
|
||||||
let hours = self.rootView.createBubbleTiemView.selectedHour.value
|
|
||||||
requestSetBubble(enable: true, keep_time: hours)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
CreateBubblePopView.show()
|
|
||||||
}
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
|
|
||||||
popupBtn.rx.tap.subscribe(onNext: { [weak self] in
|
|
||||||
guard let self = self else { return }
|
|
||||||
self.showConfirmPop(title: "温馨提醒",
|
|
||||||
message: "您确定要弹出这个气泡吗?",
|
|
||||||
confirmText: "爆裂",
|
|
||||||
confirmBlock: {
|
|
||||||
self.requestSetBubble(enable: false, keep_time: -1)
|
|
||||||
}, cancelText: "取消")
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func requestSetBubble(enable: Bool, keep_time: Int) {
|
|
||||||
DLToast.showLoading()
|
|
||||||
UserService.setBubble(enable: enable, keep_time: keep_time).subscribe(onNext: { response in
|
|
||||||
DLToast.dismiss()
|
|
||||||
guard enable else {
|
|
||||||
DLToast.show(text: "气泡已弹出") {
|
|
||||||
AppRouter.shared.popOrDismiss()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.rootView.navTitleLabel.text = "活动气泡"
|
|
||||||
self.rootView.createBubbleDoneView.messageLab.text = self.rootView.createBubbleTipsView.messageText
|
|
||||||
let endDate = Calendar.current.date(byAdding: .hour, value: keep_time, to: Date()) ?? Date()
|
|
||||||
self.rootView.createBubbleDoneView.startCountdown(endDate: endDate)
|
|
||||||
self.rootView.createBubbleTipsView.isHidden = true
|
|
||||||
self.rootView.createBubbleDoneView.isHidden = false
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
//
|
|
||||||
// CreateBubbleView.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
|
|
||||||
class CreateBubbleView: UIView {
|
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
|
||||||
|
|
||||||
var createBubbleTiemView: CreateBubbleTiemView = CreateBubbleTiemView()
|
|
||||||
var createBubbleTipsView: CreateBubbleTipsView = CreateBubbleTipsView()
|
|
||||||
var createBubbleDoneView: CreateBubbleDoneView = CreateBubbleDoneView()
|
|
||||||
|
|
||||||
private func setupRx() {
|
|
||||||
backBtn.rx.tap.subscribe(onNext: { _ in
|
|
||||||
AppRouter.shared.popOrDismiss()
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
|
|
||||||
createBubbleTiemView.onNextTap = { [weak self] in
|
|
||||||
guard let self = self else { return }
|
|
||||||
self.navTitleLabel.text = "通知您的圈子"
|
|
||||||
let hours = self.createBubbleTiemView.selectedHour.value
|
|
||||||
let endDate = Calendar.current.date(byAdding: .hour, value: hours, to: Date()) ?? Date()
|
|
||||||
let fmt = DateFormatter()
|
|
||||||
fmt.dateFormat = "HH:mm"
|
|
||||||
let timeStr = fmt.string(from: endDate)
|
|
||||||
let message = "\(timeStr)之前,我会一直在这个区域。如果需要我帮忙,请给我发消息。"
|
|
||||||
self.createBubbleTipsView.messageLab.text = message
|
|
||||||
self.createBubbleTipsView.messageText = message
|
|
||||||
self.createBubbleTiemView.isHidden = true
|
|
||||||
self.createBubbleTipsView.isHidden = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupUI() {
|
|
||||||
addSubview(createBubbleTiemView)
|
|
||||||
addSubview(createBubbleTipsView)
|
|
||||||
addSubview(createBubbleDoneView)
|
|
||||||
|
|
||||||
addSubview(navBgView)
|
|
||||||
addSubview(navBarView)
|
|
||||||
navBarView.addSubview(navTitleLabel)
|
|
||||||
navBarView.addSubview(backBtn)
|
|
||||||
|
|
||||||
navBgView.layoutChain
|
|
||||||
.edges(excludingEdge: .bottom)
|
|
||||||
.heightToWidth(160/375)
|
|
||||||
|
|
||||||
navBarView.layoutChain
|
|
||||||
.edges(excludingEdge: .bottom)
|
|
||||||
.height(kNaviHeight)
|
|
||||||
|
|
||||||
navTitleLabel.layoutChain
|
|
||||||
.top(kStatusBarHeight + 12)
|
|
||||||
.centerY(backBtn)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
backBtn.layoutChain
|
|
||||||
.centerY(navTitleLabel)
|
|
||||||
.left(15)
|
|
||||||
.width(24)
|
|
||||||
.height(24)
|
|
||||||
|
|
||||||
createBubbleTiemView.layoutChain
|
|
||||||
.topToBottomOfView(navBarView)
|
|
||||||
.edges(excludingEdge: .top)
|
|
||||||
|
|
||||||
createBubbleTipsView.layoutChain
|
|
||||||
.topToBottomOfView(navBarView)
|
|
||||||
.edges(excludingEdge: .top)
|
|
||||||
|
|
||||||
createBubbleDoneView.layoutChain
|
|
||||||
.topToBottomOfView(navBarView)
|
|
||||||
.edges(excludingEdge: .top)
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var navBgView: UIImageView = {
|
|
||||||
let iv = UIImageView()
|
|
||||||
iv.image = UIImage(named: "Common/navBar_bg_2")
|
|
||||||
iv.contentMode = .scaleAspectFill
|
|
||||||
return iv
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var navBarView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = .clear
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var navTitleLabel: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = "设置气泡时间"
|
|
||||||
label.font = .systemFont(ofSize: 18, weight: .medium)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
label.textAlignment = .center
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var backBtn: UIButton = {
|
|
||||||
let btn = UIButton(type: .custom)
|
|
||||||
btn.setImage(UIImage(named: "Common/back"), for: .normal)
|
|
||||||
btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100)
|
|
||||||
return btn
|
|
||||||
}()
|
|
||||||
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: .zero)
|
|
||||||
backgroundColor = .white
|
|
||||||
setupUI()
|
|
||||||
setupRx()
|
|
||||||
createBubbleTiemView.isHidden = false
|
|
||||||
createBubbleTipsView.isHidden = true
|
|
||||||
createBubbleDoneView.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -133,9 +133,10 @@ class GroupMemberView: UIView {
|
||||||
tableView.backgroundColor = .clear
|
tableView.backgroundColor = .clear
|
||||||
tableView.separatorStyle = .none
|
tableView.separatorStyle = .none
|
||||||
tableView.estimatedRowHeight = 76
|
tableView.estimatedRowHeight = 76
|
||||||
|
tableView.bounces = false
|
||||||
tableView.showsVerticalScrollIndicator = false
|
tableView.showsVerticalScrollIndicator = false
|
||||||
|
tableView.isScrollEnabled = false
|
||||||
tableView.register(GroupMemberCell.self)
|
tableView.register(GroupMemberCell.self)
|
||||||
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
|
|
||||||
return tableView
|
return tableView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,20 +88,21 @@ class HomeView: UIView {
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
|
||||||
// 嵌套滑动协调:contentOffset 只复位位置,scroll 状态由 pan handler + panVelocity 管理
|
// tableView 到达顶部继续下拉时,改由 GroupMemberView 的 pan 手势接管
|
||||||
groupMemberView.tableView.rx.contentOffset
|
groupMemberView.tableView.rx.contentOffset
|
||||||
.subscribe(onNext: { [weak self] offset in
|
.subscribe(onNext: { [weak self] offset in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
if !self.isSubCanScroll && offset.y != 0 {
|
if self.isSubCanScroll {
|
||||||
|
if offset.y <= 0 {
|
||||||
|
self.isSubCanScroll = false
|
||||||
|
self.groupMemberView.tableView.setContentOffset(.zero, animated: false)
|
||||||
|
}
|
||||||
|
} else if offset.y != 0 {
|
||||||
self.groupMemberView.tableView.setContentOffset(.zero, animated: false)
|
self.groupMemberView.tableView.setContentOffset(.zero, animated: false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
// 查找位置
|
|
||||||
searchLottieView.rx.tapGesture.subscribe { _ in
|
|
||||||
AppRouter.push(Route.searchLocation)
|
|
||||||
}.disposed(by: disposeBag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
|
|
@ -314,24 +315,14 @@ class HomeView: UIView {
|
||||||
// 只有 tableView 到达顶部继续下拉时,才切换为 view 拖拽
|
// 只有 tableView 到达顶部继续下拉时,才切换为 view 拖拽
|
||||||
if isSubCanScroll {
|
if isSubCanScroll {
|
||||||
let tableViewOffset = self.groupMemberView.tableView.contentOffset.y
|
let tableViewOffset = self.groupMemberView.tableView.contentOffset.y
|
||||||
if tableViewOffset > 0 {
|
if tableViewOffset > 0, newTop >= groupMemberUpLimit {
|
||||||
// 内容正在滑动,不移动 GroupMemberView
|
// 还未滑到顶部,让 tableView 处理
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 内容滑到顶部 → 切回 Pan 拖拽(用户下拉时 velocity > 0)
|
// 到达顶部继续下拉 → 关闭子滚动,交由 view 的 pan 处理
|
||||||
if pan.velocity(in: self).y > 0 || groupMemberView.frame.minY > groupMemberUpLimit + 1 {
|
|
||||||
isSubCanScroll = false
|
isSubCanScroll = false
|
||||||
panStartTop = groupMemberView.frame.minY
|
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))
|
let clamped = max(groupMemberUpLimit, min(groupMemberDownLimit, newTop))
|
||||||
topConstraint.constant = clamped - groupMemberDownLimit + 10
|
topConstraint.constant = clamped - groupMemberDownLimit + 10
|
||||||
|
|
|
||||||
|
|
@ -127,11 +127,6 @@ class HomeViewController: BaseViewController {
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
private func reactiveAction() {
|
private func reactiveAction() {
|
||||||
// 气泡
|
|
||||||
rootView.bubbleView.rx.tapGesture.subscribe { _ in
|
|
||||||
AppRouter.push(Route.createBubble)
|
|
||||||
}.disposed(by: disposeBag)
|
|
||||||
|
|
||||||
// 签到
|
// 签到
|
||||||
rootView.signInView.rx.tapGesture.subscribe { _ in
|
rootView.signInView.rx.tapGesture.subscribe { _ in
|
||||||
let vc = SignInVC(lastLocation: self.lastLocation)
|
let vc = SignInVC(lastLocation: self.lastLocation)
|
||||||
|
|
@ -187,31 +182,6 @@ class HomeViewController: BaseViewController {
|
||||||
self?.requestGroupInfo()
|
self?.requestGroupInfo()
|
||||||
}.disposed(by: disposeBag)
|
}.disposed(by: disposeBag)
|
||||||
|
|
||||||
// 查找位置(查看成员位置)
|
|
||||||
NotificationCenter.default.rx.notification(.ShowMemberLocationNotification, object: nil)
|
|
||||||
.subscribe { [weak self] notification in
|
|
||||||
guard let self = self,
|
|
||||||
let userInfo = notification.userInfo,
|
|
||||||
let last_position = userInfo["last_position"] as? String,
|
|
||||||
let group_key = userInfo["group_key"] as? String else { return }
|
|
||||||
// 解析坐标 "lat:lng:address"
|
|
||||||
let parts = last_position.components(separatedBy: ":")
|
|
||||||
let coord: CLLocationCoordinate2D?
|
|
||||||
if parts.count >= 2, let lat = Double(parts[0]), let lng = Double(parts[1]) {
|
|
||||||
coord = CLLocationCoordinate2D(latitude: lat, longitude: lng)
|
|
||||||
} else {
|
|
||||||
coord = nil
|
|
||||||
}
|
|
||||||
// 切换到成员所在圈子,完成后定位
|
|
||||||
GroupService.operate(opType: "setdefault", requestData: ["group_key": group_key]).subscribe { _ in
|
|
||||||
self.requestGroupInfo()
|
|
||||||
if let c = coord, CLLocationCoordinate2DIsValid(c) {
|
|
||||||
self.rootView.mapView.setCenter(c, animated: true)
|
|
||||||
self.rootView.mapView.setZoomLevel(16, animated: true)
|
|
||||||
}
|
|
||||||
}.disposed(by: self.disposeBag)
|
|
||||||
}.disposed(by: disposeBag)
|
|
||||||
|
|
||||||
// 面板关闭回调
|
// 面板关闭回调
|
||||||
rootView.onDismissPanel = { [weak self] in
|
rootView.onDismissPanel = { [weak self] in
|
||||||
self?.isMemberPanelShown = false
|
self?.isMemberPanelShown = false
|
||||||
|
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
//
|
|
||||||
// SearchLocationResultVC.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/27.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import Lottie
|
|
||||||
|
|
||||||
class SearchLocationResultVC: BaseViewController {
|
|
||||||
|
|
||||||
fileprivate var rootView: SearchLocationResultView!
|
|
||||||
|
|
||||||
override func loadView() {
|
|
||||||
rootView = SearchLocationResultView(frame: UIScreen.main.bounds)
|
|
||||||
view = rootView
|
|
||||||
}
|
|
||||||
|
|
||||||
private let phone: String
|
|
||||||
private let code: Int
|
|
||||||
private let memberData: [String : Any]
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
rootView.unlockVipView.isHidden = AppContextManager.shared.vip > 1
|
|
||||||
AppContextManager.shared.vip > 1 ? nil : rootView.unlockVipLottieView.play()
|
|
||||||
|
|
||||||
reactiveAction()
|
|
||||||
requestPhoneArea()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func reactiveAction() {
|
|
||||||
rootView.successBtn.rx.tap.subscribe(onNext: { _ in
|
|
||||||
if self.code == 0 { // 去查看
|
|
||||||
NotificationCenter.default.post(name: .ShowMemberLocationNotification,
|
|
||||||
object: nil,
|
|
||||||
userInfo: self.memberData)
|
|
||||||
AppRouter.shared.popToRoot()
|
|
||||||
}
|
|
||||||
else { // 邀请Ta
|
|
||||||
|
|
||||||
}
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handleCode() {
|
|
||||||
guard AppContextManager.shared.vip > 1 else { return }
|
|
||||||
|
|
||||||
var fileName = ""
|
|
||||||
var message = ""
|
|
||||||
var tips = ""
|
|
||||||
switch code {
|
|
||||||
case 0: // 圈子成员
|
|
||||||
fileName = "phone_search_success"
|
|
||||||
message = "定位成功"
|
|
||||||
tips = "点击去查看可获得具体位置"
|
|
||||||
rootView.inviteView.isHidden = false
|
|
||||||
rootView.successTipsLab.text = "已获得具体位置"
|
|
||||||
rootView.successBtn.setTitle("去查看", for: .normal)
|
|
||||||
rootView.phoneLab.text = phone
|
|
||||||
rootView.successLottieView.play()
|
|
||||||
case 20004: // 未注册
|
|
||||||
fileName = "phone_search_fail"
|
|
||||||
message = "此用户未注册"
|
|
||||||
tips = "根据最新的相关法规,需用户双方均安装该APP才可实现定位,未安装APP的用户将不再提供具体定位功能。"
|
|
||||||
rootView.inviteBtn.isHidden = false
|
|
||||||
case 20005: // 不在一个圈子里
|
|
||||||
fileName = "phone_search_success"
|
|
||||||
message = "定位成功"
|
|
||||||
tips = "根据最新的相关法规,您查询的用户与您不在同一个圈子时, 不再提供具体的位置定位,邀请ta加入你的圈子后可随时查看位置。"
|
|
||||||
rootView.inviteView.isHidden = false
|
|
||||||
rootView.successTipsLab.text = "邀请加入圈子后分享位置"
|
|
||||||
rootView.successBtn.setTitle("邀请他", for: .normal)
|
|
||||||
rootView.phoneLab.text = phone
|
|
||||||
rootView.successLottieView.play()
|
|
||||||
requestGroupInfo()
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if let path = Bundle.main.path(forResource: fileName, ofType: "json") {
|
|
||||||
self.rootView.normalLottieView.animation = LottieAnimation.filepath(path)
|
|
||||||
self.rootView.normalLottieView.play()
|
|
||||||
}
|
|
||||||
self.rootView.messageLab.text = message
|
|
||||||
self.rootView.tipsLab.text = tips
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 手机号归属地
|
|
||||||
private func requestPhoneArea() {
|
|
||||||
SystemService.phoneArea(phone: phone).subscribe(onNext: { response in
|
|
||||||
guard let data = response.data else { return }
|
|
||||||
self.handleCode()
|
|
||||||
let province = data["Province"].safeString
|
|
||||||
let city = data["City"].safeString
|
|
||||||
self.rootView.phoneAreaLab.text = "\(province)·\(city)"
|
|
||||||
self.rootView.unlockVipPhoneAreaLab.text = "\(province)·\(city)"
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 圈子列表
|
|
||||||
private func requestGroupInfo() {
|
|
||||||
GroupService.groupInfo().subscribe { response in
|
|
||||||
guard let model = response.model else { return }
|
|
||||||
|
|
||||||
}.disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Init
|
|
||||||
init(phone: String, code: Int, memberData: [String : Any] = [:]) {
|
|
||||||
self.phone = phone
|
|
||||||
self.code = code
|
|
||||||
self.memberData = memberData
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,414 +0,0 @@
|
||||||
//
|
|
||||||
// SearchLocationResultView.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/27.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import Lottie
|
|
||||||
|
|
||||||
class SearchLocationResultView: UIView {
|
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
|
||||||
|
|
||||||
private func setupRx() {
|
|
||||||
backBtn.rx.tap.subscribe(onNext: { _ in
|
|
||||||
AppRouter.shared.popOrDismiss()
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupUI() {
|
|
||||||
addSubview(unlockVipView)
|
|
||||||
addSubview(navBgView)
|
|
||||||
addSubview(navBarView)
|
|
||||||
navBarView.addSubview(navTitleLabel)
|
|
||||||
navBarView.addSubview(backBtn)
|
|
||||||
|
|
||||||
addSubview(normalView)
|
|
||||||
addSubview(successLottieView)
|
|
||||||
|
|
||||||
navBgView.layoutChain
|
|
||||||
.edges(excludingEdge: .bottom)
|
|
||||||
.heightToWidth(160/375)
|
|
||||||
|
|
||||||
navBarView.layoutChain
|
|
||||||
.edges(excludingEdge: .bottom)
|
|
||||||
.height(kNaviHeight)
|
|
||||||
|
|
||||||
navTitleLabel.layoutChain
|
|
||||||
.top(kStatusBarHeight + 12)
|
|
||||||
.centerY(backBtn)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
backBtn.layoutChain
|
|
||||||
.centerY(navTitleLabel)
|
|
||||||
.left(15)
|
|
||||||
.width(24)
|
|
||||||
.height(24)
|
|
||||||
|
|
||||||
unlockVipView.layoutChain.edges()
|
|
||||||
|
|
||||||
successLottieView.layoutChain.edges()
|
|
||||||
|
|
||||||
normalView.layoutChain
|
|
||||||
.topToBottomOfView(navBarView)
|
|
||||||
.edges(excludingEdge: .top)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Views
|
|
||||||
|
|
||||||
lazy var navBgView: UIImageView = {
|
|
||||||
let iv = UIImageView()
|
|
||||||
iv.image = UIImage(named: "Common/navBar_bg_2")
|
|
||||||
iv.contentMode = .scaleAspectFill
|
|
||||||
return iv
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var navBarView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = .clear
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var navTitleLabel: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = " "
|
|
||||||
label.font = .systemFont(ofSize: 18, weight: .medium)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
label.textAlignment = .center
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var backBtn: UIButton = {
|
|
||||||
let btn = UIButton(type: .custom)
|
|
||||||
btn.setImage(UIImage(named: "Common/back"), for: .normal)
|
|
||||||
btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100)
|
|
||||||
return btn
|
|
||||||
}()
|
|
||||||
|
|
||||||
// MARK: - 成功烟花Lottie
|
|
||||||
lazy var successLottieView: LottieAnimationView = {
|
|
||||||
let view = LottieAnimationView(name: "phone_search_fireworks")
|
|
||||||
view.loopMode = .loop
|
|
||||||
view.isUserInteractionEnabled = false
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
// MARK: - 已解锁vip
|
|
||||||
lazy var normalView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = .clear
|
|
||||||
|
|
||||||
view.addSubview(normalLottieView)
|
|
||||||
normalLottieView.layoutChain
|
|
||||||
.top(20)
|
|
||||||
.width(270)
|
|
||||||
.heightToWidth(1)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
view.addSubview(phoneAreaLab)
|
|
||||||
phoneAreaLab.layoutChain
|
|
||||||
.topToBottomOfView(normalLottieView)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
view.addSubview(messageLab)
|
|
||||||
messageLab.layoutChain
|
|
||||||
.topToBottomOfView(phoneAreaLab, offset: 10)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
view.addSubview(tipsLab)
|
|
||||||
tipsLab.layoutChain
|
|
||||||
.topToBottomOfView(messageLab, offset: 15)
|
|
||||||
.edgesHorzontal(31)
|
|
||||||
|
|
||||||
view.addSubview(inviteBtn)
|
|
||||||
inviteBtn.layoutChain
|
|
||||||
.topToBottomOfView(tipsLab, offset: 30)
|
|
||||||
.edgesHorzontal(68)
|
|
||||||
.height(50)
|
|
||||||
|
|
||||||
view.addSubview(inviteView)
|
|
||||||
inviteView.layoutChain
|
|
||||||
.topToBottomOfView(tipsLab, offset: 25)
|
|
||||||
.edgesHorzontal(15)
|
|
||||||
.height(80)
|
|
||||||
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var phoneAreaLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = " "
|
|
||||||
label.font = .systemFont(ofSize: 20, weight: .semibold)
|
|
||||||
label.textColor = UIColor(hexStr: "#16B3FF")
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var messageLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.font = .systemFont(ofSize: 16, weight: .bold)
|
|
||||||
label.textColor = UIColor(hexStr: "#333333")
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var tipsLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.font = .systemFont(ofSize: 12, weight: .medium)
|
|
||||||
label.textColor = UIColor(hexStr: "#999999")
|
|
||||||
label.numberOfLines = 0
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var normalLottieView: LottieAnimationView = {
|
|
||||||
let view = LottieAnimationView()
|
|
||||||
view.loopMode = .loop
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var inviteBtn: UIButton = {
|
|
||||||
let btn = UIButton(type: .custom)
|
|
||||||
btn.setTitle("邀请Ta加入", 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.isHidden = true
|
|
||||||
return btn
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var inviteView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = UIColor(hexStr: "#EFF9FF")
|
|
||||||
view.cornerRadius = 16
|
|
||||||
view.isHidden = true
|
|
||||||
|
|
||||||
view.addSubview(successBtn)
|
|
||||||
successBtn.layoutChain
|
|
||||||
.right(15)
|
|
||||||
.centerY()
|
|
||||||
.width(90)
|
|
||||||
.height(36)
|
|
||||||
|
|
||||||
view.addSubview(successTipsLab)
|
|
||||||
successTipsLab.layoutChain
|
|
||||||
.topToCenterYOfView(view)
|
|
||||||
.left(15)
|
|
||||||
|
|
||||||
view.addSubview(phoneLab)
|
|
||||||
phoneLab.layoutChain
|
|
||||||
.bottomToCenterYOfView(view)
|
|
||||||
.left(15)
|
|
||||||
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var phoneLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.font = .systemFont(ofSize: 16, weight: .bold)
|
|
||||||
label.textColor = UIColor(hexStr: "#333333")
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var successTipsLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = "邀请加入圈子后分享位置"
|
|
||||||
label.font = .systemFont(ofSize: 10, weight: .medium)
|
|
||||||
label.textColor = UIColor(hexStr: "#999999")
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var successBtn: UIButton = {
|
|
||||||
let btn = UIButton(type: .custom)
|
|
||||||
btn.setTitle("邀请Ta", for: .normal)
|
|
||||||
btn.setTitleColor(.white, for: .normal)
|
|
||||||
btn.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
|
|
||||||
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
|
||||||
btn.cornerRadius = 18
|
|
||||||
return btn
|
|
||||||
}()
|
|
||||||
|
|
||||||
// MARK: - 未解锁vip
|
|
||||||
lazy var unlockVipView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = .clear
|
|
||||||
view.clipsToBounds = true
|
|
||||||
view.isHidden = true
|
|
||||||
|
|
||||||
let bgImgView = UIImageView(image: UIImage(named: "SearchLocation/result_bg"))
|
|
||||||
bgImgView.contentMode = .scaleAspectFill
|
|
||||||
view.addSubview(bgImgView)
|
|
||||||
bgImgView.layoutChain
|
|
||||||
.edges(excludingEdge: .bottom)
|
|
||||||
.heightToWidth(457/357)
|
|
||||||
|
|
||||||
view.addSubview(unlockVipLottieView)
|
|
||||||
unlockVipLottieView.layoutChain
|
|
||||||
.edgesHorzontal(60)
|
|
||||||
.heightToWidth(1)
|
|
||||||
.top(150)
|
|
||||||
// .bottomToView(bgImgView, offset: -15)
|
|
||||||
|
|
||||||
view.addSubview(unlockVipPhoneAreaLab)
|
|
||||||
unlockVipPhoneAreaLab.layoutChain
|
|
||||||
.topToBottomOfView(unlockVipLottieView)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
let locationView = UIView()
|
|
||||||
locationView.backgroundColor = .clear
|
|
||||||
view.addSubview(locationView)
|
|
||||||
locationView.layoutChain
|
|
||||||
.topToBottomOfView(unlockVipPhoneAreaLab, offset: 10)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
let locationTitleLab = UILabel()
|
|
||||||
locationTitleLab.text = "位置:"
|
|
||||||
locationTitleLab.font = .systemFont(ofSize: 16, weight: .bold)
|
|
||||||
locationTitleLab.textColor = UIColor(hexStr: "#16B3FF")
|
|
||||||
locationView.addSubview(locationTitleLab)
|
|
||||||
locationTitleLab.layoutChain
|
|
||||||
.left()
|
|
||||||
.centerY()
|
|
||||||
|
|
||||||
let maskView = UIImageView(image: UIImage(named: "SearchLocation/mask"))
|
|
||||||
locationView.addSubview(maskView)
|
|
||||||
maskView.layoutChain
|
|
||||||
.leftToRightOfView(locationTitleLab)
|
|
||||||
.right()
|
|
||||||
.edgesVertical()
|
|
||||||
.width(144)
|
|
||||||
.height(30)
|
|
||||||
|
|
||||||
let unlockVipBtn = UIButton()
|
|
||||||
unlockVipBtn.setTitle("开通会员", for: .normal)
|
|
||||||
unlockVipBtn.setTitleColor(.white, for: .normal)
|
|
||||||
unlockVipBtn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
|
||||||
unlockVipBtn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
|
||||||
unlockVipBtn.cornerRadius = 25
|
|
||||||
unlockVipBtn.rx.tap.subscribe(onNext: { _ in
|
|
||||||
AppRouter.push(Route.vipRecharge)
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
|
|
||||||
view.addSubview(unlockVipBtn)
|
|
||||||
unlockVipBtn.layoutChain
|
|
||||||
.edgesHorzontal(15)
|
|
||||||
.bottom(kSafeBottomMargin + 20)
|
|
||||||
.height(50)
|
|
||||||
|
|
||||||
// 最后出现地点
|
|
||||||
let tipsDot3 = UIView()
|
|
||||||
tipsDot3.backgroundColor = UIColor(hexStr: "#16B3FF")
|
|
||||||
tipsDot3.cornerRadius = 2.5
|
|
||||||
let tipsLab3 = UILabel()
|
|
||||||
tipsLab3.text = "最后出现地点"
|
|
||||||
tipsLab3.font = .systemFont(ofSize: 12, weight: .medium)
|
|
||||||
tipsLab3.textColor = UIColor(hexStr: "#333333")
|
|
||||||
view.addSubview(tipsLab3)
|
|
||||||
view.addSubview(tipsDot3)
|
|
||||||
tipsLab3.layoutChain
|
|
||||||
.bottomToTopOfView(unlockVipBtn, offset: -20)
|
|
||||||
.rightToCenterXOfView(view, offset: 30)
|
|
||||||
|
|
||||||
tipsDot3.layoutChain
|
|
||||||
.rightToLeftOfView(tipsLab3, offset: -8)
|
|
||||||
.width(5)
|
|
||||||
.heightToWidth(1)
|
|
||||||
.centerY(tipsLab3)
|
|
||||||
|
|
||||||
// "近24小时轨迹"
|
|
||||||
let tipsDot4 = UIView()
|
|
||||||
tipsDot4.backgroundColor = UIColor(hexStr: "#16B3FF")
|
|
||||||
tipsDot4.cornerRadius = 2.5
|
|
||||||
let tipsLab4 = UILabel()
|
|
||||||
tipsLab4.text = "近24小时轨迹"
|
|
||||||
tipsLab4.font = .systemFont(ofSize: 12, weight: .medium)
|
|
||||||
tipsLab4.textColor = UIColor(hexStr: "#333333")
|
|
||||||
view.addSubview(tipsLab4)
|
|
||||||
view.addSubview(tipsDot4)
|
|
||||||
tipsDot4.layoutChain
|
|
||||||
.leftToCenterXOfView(view, offset: 30)
|
|
||||||
.width(5)
|
|
||||||
.heightToWidth(1)
|
|
||||||
.centerY(tipsLab4)
|
|
||||||
tipsLab4.layoutChain
|
|
||||||
.bottomToTopOfView(unlockVipBtn, offset: -20)
|
|
||||||
.leftToRightOfView(tipsDot4, offset: 8)
|
|
||||||
|
|
||||||
// 是否在线
|
|
||||||
let tipsDot1 = UIView()
|
|
||||||
tipsDot1.backgroundColor = UIColor(hexStr: "#16B3FF")
|
|
||||||
tipsDot1.cornerRadius = 2.5
|
|
||||||
let tipsLab1 = UILabel()
|
|
||||||
tipsLab1.text = "是否在线"
|
|
||||||
tipsLab1.font = .systemFont(ofSize: 12, weight: .medium)
|
|
||||||
tipsLab1.textColor = UIColor(hexStr: "#333333")
|
|
||||||
view.addSubview(tipsLab1)
|
|
||||||
view.addSubview(tipsDot1)
|
|
||||||
tipsDot1.layoutChain
|
|
||||||
.leftToView(tipsDot3)
|
|
||||||
.width(5)
|
|
||||||
.heightToWidth(1)
|
|
||||||
.centerY(tipsLab1)
|
|
||||||
tipsLab1.layoutChain
|
|
||||||
.bottomToTopOfView(tipsLab3, offset: -10)
|
|
||||||
.leftToRightOfView(tipsDot1, offset: 8)
|
|
||||||
|
|
||||||
// 实时位置变化
|
|
||||||
let tipsDot2 = UIView()
|
|
||||||
tipsDot2.backgroundColor = UIColor(hexStr: "#16B3FF")
|
|
||||||
tipsDot2.cornerRadius = 2.5
|
|
||||||
let tipsLab2 = UILabel()
|
|
||||||
tipsLab2.text = "实时位置变化"
|
|
||||||
tipsLab2.font = .systemFont(ofSize: 12, weight: .medium)
|
|
||||||
tipsLab2.textColor = UIColor(hexStr: "#333333")
|
|
||||||
view.addSubview(tipsLab2)
|
|
||||||
view.addSubview(tipsDot2)
|
|
||||||
tipsDot2.layoutChain
|
|
||||||
.leftToView(tipsDot4)
|
|
||||||
.width(5)
|
|
||||||
.heightToWidth(1)
|
|
||||||
.centerY(tipsLab2)
|
|
||||||
tipsLab2.layoutChain
|
|
||||||
.bottomToTopOfView(tipsLab4, offset: -10)
|
|
||||||
.leftToRightOfView(tipsDot2, offset: 8)
|
|
||||||
|
|
||||||
// 再迈一步,你就能看到:
|
|
||||||
let unlockVipTitleLab = UILabel()
|
|
||||||
unlockVipTitleLab.text = "再迈一步,你就能看到:"
|
|
||||||
unlockVipTitleLab.font = .systemFont(ofSize: 14, weight: .medium)
|
|
||||||
unlockVipTitleLab.textColor = UIColor(hexStr: "#16B3FF")
|
|
||||||
view.addSubview(unlockVipTitleLab)
|
|
||||||
unlockVipTitleLab.layoutChain
|
|
||||||
.bottomToTopOfView(tipsLab1, offset: -8)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var unlockVipLottieView: LottieAnimationView = {
|
|
||||||
let view = LottieAnimationView(name: "phone_search_no_vip")
|
|
||||||
view.loopMode = .loop
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var unlockVipPhoneAreaLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = " "
|
|
||||||
label.font = .systemFont(ofSize: 20, weight: .semibold)
|
|
||||||
label.textColor = UIColor(hexStr: "#16B3FF")
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: .zero)
|
|
||||||
backgroundColor = .white
|
|
||||||
setupUI()
|
|
||||||
setupRx()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
//
|
|
||||||
// SearchLocationVC.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/27.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import Lottie
|
|
||||||
|
|
||||||
class SearchLocationVC: BaseViewController {
|
|
||||||
|
|
||||||
fileprivate var rootView: SearchLocationView!
|
|
||||||
|
|
||||||
override func loadView() {
|
|
||||||
rootView = SearchLocationView(frame: UIScreen.main.bounds)
|
|
||||||
view = rootView
|
|
||||||
}
|
|
||||||
|
|
||||||
private var searchBtn: UIButton {
|
|
||||||
rootView.searchBtn
|
|
||||||
}
|
|
||||||
|
|
||||||
private var code: Int = -999
|
|
||||||
private var memberData: [String : Any] = [:]
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
searchBtn.rx.tap.subscribe(onNext: { _ in
|
|
||||||
guard let phone = self.rootView.phoneInputTF.text, phone.isPhoneNumber else {
|
|
||||||
DLToast.show(text: "请输入正确的手机号码")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.requestSearchPhone(phone: phone)
|
|
||||||
self.rootView.searchProgressView.isHidden = false
|
|
||||||
self.rootView.searchPhoneLottieView.play()
|
|
||||||
self.rootView.playVideo {
|
|
||||||
self.rootView.videoView.isHidden = true
|
|
||||||
self.rootView.searchProgressView.isHidden = true
|
|
||||||
self.rootView.searchPhoneLottieView.stop()
|
|
||||||
|
|
||||||
guard self.code != -999 else {
|
|
||||||
DLToast.show(text: "发生未知错误,请重试")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.code == 0 {
|
|
||||||
AppRouter.push(Route.searchLocationResult, userInfo: ["phone": phone,
|
|
||||||
"code": self.code,
|
|
||||||
"memberData": self.memberData])
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
AppRouter.push(Route.searchLocationResult, userInfo: ["phone": phone, "code": self.code])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
|
||||||
super.viewWillAppear(animated)
|
|
||||||
rootView.startMarqueeAnimation()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 查找号码
|
|
||||||
private func requestSearchPhone(phone: String) {
|
|
||||||
SystemService.search(op_type: "phone",
|
|
||||||
number: phone).subscribe(onNext: { response in
|
|
||||||
self.code = 0
|
|
||||||
/**
|
|
||||||
"group_name" : "",
|
|
||||||
"user_id" : "X16097989",
|
|
||||||
|
|
||||||
"is_online" : true,
|
|
||||||
"group_key" : "smartdrive\/X2804080\/1002",
|
|
||||||
"last_position" : "29.613138:106.509789:星光一路"
|
|
||||||
*/
|
|
||||||
if let data = response.data {
|
|
||||||
self.memberData = data
|
|
||||||
}
|
|
||||||
}, onError: { (error) in
|
|
||||||
guard let code = error.underlyingError?.code else { return }
|
|
||||||
self.code = code
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,543 +0,0 @@
|
||||||
//
|
|
||||||
// SearchLocationView.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/27.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import AVFoundation
|
|
||||||
import Lottie
|
|
||||||
|
|
||||||
class SearchLocationView: UIView {
|
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
|
||||||
let onVideoComplete = PublishSubject<Void>()
|
|
||||||
private var player: AVPlayer?
|
|
||||||
private var timeObserver: Any?
|
|
||||||
|
|
||||||
private func setupRx() {
|
|
||||||
backBtn.rx.tap.subscribe(onNext: { _ in
|
|
||||||
AppRouter.shared.popOrDismiss()
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
|
|
||||||
phoneInputTF.rx.text
|
|
||||||
.map { text -> String? in
|
|
||||||
guard let text = text else { return nil }
|
|
||||||
return String(text.prefix(11))
|
|
||||||
}.bind(to: phoneInputTF.rx.text)
|
|
||||||
.disposed(by: disposeBag)
|
|
||||||
|
|
||||||
phoneInputTF.rx.text.orEmpty.map { phone -> Bool in
|
|
||||||
if phone.count == 11 {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bind(to: searchBtn.rx.isEnabled)
|
|
||||||
.disposed(by: disposeBag)
|
|
||||||
|
|
||||||
phoneInputTF.rx.controlEvent(.editingDidEndOnExit)
|
|
||||||
.subscribe(onNext: { [weak self] in
|
|
||||||
guard let self = self else { return }
|
|
||||||
self.phoneInputTF.resignFirstResponder()
|
|
||||||
})
|
|
||||||
.disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupUI() {
|
|
||||||
addSubview(navBgView)
|
|
||||||
addSubview(navBarView)
|
|
||||||
navBarView.addSubview(navTitleLabel)
|
|
||||||
navBarView.addSubview(backBtn)
|
|
||||||
addSubview(scrollView)
|
|
||||||
addSubview(videoView)
|
|
||||||
addSubview(searchProgressView)
|
|
||||||
|
|
||||||
videoView.layoutChain.edges()
|
|
||||||
|
|
||||||
searchProgressView.layoutChain
|
|
||||||
.edgesHorzontal(15)
|
|
||||||
.bottom(kSafeBottomMargin + 20)
|
|
||||||
.height(221)
|
|
||||||
|
|
||||||
navBgView.layoutChain
|
|
||||||
.edges(excludingEdge: .bottom)
|
|
||||||
.heightToWidth(160/375)
|
|
||||||
|
|
||||||
navBarView.layoutChain
|
|
||||||
.edges(excludingEdge: .bottom)
|
|
||||||
.height(kNaviHeight)
|
|
||||||
|
|
||||||
navTitleLabel.layoutChain
|
|
||||||
.top(kStatusBarHeight + 12)
|
|
||||||
.centerY(backBtn)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
backBtn.layoutChain
|
|
||||||
.centerY(navTitleLabel)
|
|
||||||
.left(15)
|
|
||||||
.width(24)
|
|
||||||
.height(24)
|
|
||||||
|
|
||||||
scrollView.layoutChain
|
|
||||||
.topToBottomOfView(navBarView)
|
|
||||||
.edges(excludingEdge: .top)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Marquee
|
|
||||||
private func randomPhoneNumber() -> String {
|
|
||||||
// 所有合法前3位号段
|
|
||||||
let prefixes = [
|
|
||||||
"134","135","136","137","138","139","147","150","151","152","157","158","159","178","182","183","184","187","188","198",
|
|
||||||
"130","131","132","145","155","156","166","175","176","185","186","196",
|
|
||||||
"133","149","153","173","177","180","181","189","191","199"
|
|
||||||
]
|
|
||||||
// 随机选一个号段
|
|
||||||
let prefix = prefixes.randomElement()!
|
|
||||||
|
|
||||||
// 随机生成后面8位数字
|
|
||||||
var suffix = ""
|
|
||||||
for _ in 0..<8 {
|
|
||||||
suffix.append("\(Int.random(in: 0...9))")
|
|
||||||
}
|
|
||||||
|
|
||||||
return prefix + suffix
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupMarquee() {
|
|
||||||
let surnames = ["张", "李", "王", "陈", "刘", "杨", "赵", "黄", "周", "吴",
|
|
||||||
"林", "何", "马", "胡", "郑", "梁", "谢", "宋", "唐", "韩"]
|
|
||||||
|
|
||||||
let container = UIView()
|
|
||||||
container.backgroundColor = .clear
|
|
||||||
marqueeScrollView.addSubview(container)
|
|
||||||
|
|
||||||
var prevView: UIView?
|
|
||||||
for _ in 0..<10 {
|
|
||||||
let iconIndex = Int.random(in: 1...15)
|
|
||||||
let surname = surnames.randomElement() ?? "张"
|
|
||||||
let maskedName = "\(surname)**"
|
|
||||||
let phone = randomPhoneNumber()
|
|
||||||
let maskedPhone = phone.prefix(3) + "******" + phone.suffix(2)
|
|
||||||
|
|
||||||
let itemView = UIView()
|
|
||||||
itemView.backgroundColor = UIColor(hexStr: "#EFF9FF")
|
|
||||||
itemView.cornerRadius = 6
|
|
||||||
container.addSubview(itemView)
|
|
||||||
|
|
||||||
let avatar = UIImageView(image: UIImage(named: "UserIcon/\(iconIndex)"))
|
|
||||||
avatar.contentMode = .scaleAspectFill
|
|
||||||
avatar.cornerRadius = 12
|
|
||||||
avatar.clipsToBounds = true
|
|
||||||
itemView.addSubview(avatar)
|
|
||||||
|
|
||||||
let label = UILabel()
|
|
||||||
label.font = .systemFont(ofSize: 13, weight: .medium)
|
|
||||||
let fullText = "\(maskedName) 定位到了 \(maskedPhone)"
|
|
||||||
let attr = NSMutableAttributedString(string: fullText)
|
|
||||||
attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#16B3FF"),
|
|
||||||
range: NSRange(fullText.range(of: maskedName)!, in: fullText))
|
|
||||||
if let phoneRange = fullText.range(of: maskedPhone) {
|
|
||||||
attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#16B3FF"),
|
|
||||||
range: NSRange(phoneRange, in: fullText))
|
|
||||||
}
|
|
||||||
label.attributedText = attr
|
|
||||||
itemView.addSubview(label)
|
|
||||||
|
|
||||||
avatar.layoutChain
|
|
||||||
.left(10).centerY()
|
|
||||||
.width(24).height(24)
|
|
||||||
|
|
||||||
label.layoutChain
|
|
||||||
.leftToRightOfView(avatar, offset: 6)
|
|
||||||
.centerY().right(10)
|
|
||||||
|
|
||||||
itemView.layoutChain
|
|
||||||
.centerY()
|
|
||||||
.height(30)
|
|
||||||
|
|
||||||
if let prev = prevView {
|
|
||||||
itemView.layoutChain.leftToRightOfView(prev, offset: 20)
|
|
||||||
} else {
|
|
||||||
itemView.layoutChain.left()
|
|
||||||
}
|
|
||||||
prevView = itemView
|
|
||||||
}
|
|
||||||
|
|
||||||
if let last = prevView {
|
|
||||||
container.layoutChain
|
|
||||||
.edges().heightToView(marqueeScrollView)
|
|
||||||
.rightToView(last)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func startMarqueeAnimation() {
|
|
||||||
marqueeScrollView.layer.removeAllAnimations()
|
|
||||||
marqueeScrollView.contentOffset.x = 0
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
|
||||||
guard let self = self else { return }
|
|
||||||
let contentW = self.marqueeScrollView.contentSize.width
|
|
||||||
guard contentW > self.marqueeScrollView.bounds.width else { return }
|
|
||||||
let duration = contentW / 100
|
|
||||||
|
|
||||||
UIView.animate(withDuration: duration, delay: 0, options: [.curveLinear, .repeat]) {
|
|
||||||
self.marqueeScrollView.contentOffset.x = contentW - self.marqueeScrollView.bounds.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func willMove(toWindow newWindow: UIWindow?) {
|
|
||||||
super.willMove(toWindow: newWindow)
|
|
||||||
if newWindow != nil {
|
|
||||||
startMarqueeAnimation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Views
|
|
||||||
|
|
||||||
lazy var navBgView: UIImageView = {
|
|
||||||
let iv = UIImageView()
|
|
||||||
iv.image = UIImage(named: "Common/navBar_bg_2")
|
|
||||||
iv.contentMode = .scaleAspectFill
|
|
||||||
return iv
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var navBarView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = .clear
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var navTitleLabel: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = "找人助手"
|
|
||||||
label.font = .systemFont(ofSize: 18, weight: .medium)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
label.textAlignment = .center
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var backBtn: UIButton = {
|
|
||||||
let btn = UIButton(type: .custom)
|
|
||||||
btn.setImage(UIImage(named: "Common/back"), for: .normal)
|
|
||||||
btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100)
|
|
||||||
return btn
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var scrollView: UIScrollView = {
|
|
||||||
let view = UIScrollView()
|
|
||||||
view.backgroundColor = .clear
|
|
||||||
view.showsVerticalScrollIndicator = false
|
|
||||||
view.bounces = false
|
|
||||||
|
|
||||||
let contentView = UIView()
|
|
||||||
contentView.backgroundColor = .clear
|
|
||||||
view.addSubview(contentView)
|
|
||||||
contentView.layoutChain.edges().widthToView(view)
|
|
||||||
|
|
||||||
let bgImgView = UIImageView()
|
|
||||||
bgImgView.image = UIImage(named: "SearchLocation/bg_1")
|
|
||||||
contentView.addSubview(bgImgView)
|
|
||||||
bgImgView.layoutChain
|
|
||||||
.top(15)
|
|
||||||
.edgesHorzontal(56.5)
|
|
||||||
.heightToWidth(626/524)
|
|
||||||
|
|
||||||
contentView.addSubview(titleLab)
|
|
||||||
titleLab.layoutChain
|
|
||||||
.topToBottomOfView(bgImgView, offset: 0)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
contentView.addSubview(marqueeScrollView)
|
|
||||||
marqueeScrollView.layoutChain
|
|
||||||
.topToBottomOfView(titleLab, offset: 10)
|
|
||||||
.edgesHorzontal()
|
|
||||||
.height(30)
|
|
||||||
|
|
||||||
contentView.addSubview(searchInputView)
|
|
||||||
searchInputView.layoutChain
|
|
||||||
.topToBottomOfView(marqueeScrollView, offset: 20)
|
|
||||||
.edgesHorzontal(15)
|
|
||||||
.bottom(kSafeBottomMargin + 30)
|
|
||||||
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var videoView: UIView = {
|
|
||||||
let v = UIView()
|
|
||||||
v.backgroundColor = .black
|
|
||||||
v.isHidden = true
|
|
||||||
|
|
||||||
return v
|
|
||||||
}()
|
|
||||||
|
|
||||||
func playVideo(completion: (() -> Void)? = nil) {
|
|
||||||
guard let path = Bundle.main.path(forResource: "search_interlude", ofType: "mp4") ?? Bundle.main.path(forResource: "search_interlude", ofType: "mp4", inDirectory: "video") else {
|
|
||||||
print("[video] file not found in bundle: \(Bundle.main.resourcePath ?? "")")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
videoView.isHidden = false
|
|
||||||
videoView.layoutIfNeeded()
|
|
||||||
|
|
||||||
let player = AVPlayer(url: URL(fileURLWithPath: path))
|
|
||||||
self.player = player
|
|
||||||
let playerLayer = AVPlayerLayer(player: player)
|
|
||||||
playerLayer.frame = videoView.bounds.isEmpty ? UIScreen.main.bounds : videoView.bounds
|
|
||||||
playerLayer.videoGravity = .resizeAspectFill
|
|
||||||
videoView.layer.addSublayer(playerLayer)
|
|
||||||
|
|
||||||
// 进度跟踪
|
|
||||||
timeObserver = player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.1, preferredTimescale: 600), queue: .main) { [weak self] time in
|
|
||||||
guard let self = self, let duration = self.player?.currentItem?.duration.seconds, duration > 0 else { return }
|
|
||||||
self.updateVideoProgress(Float(time.seconds / duration))
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationCenter.default.rx.notification(.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
|
|
||||||
.take(1)
|
|
||||||
.subscribe(onNext: { _ in
|
|
||||||
self.updateVideoProgress(1)
|
|
||||||
completion?()
|
|
||||||
self.onVideoComplete.onNext(())
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
|
|
||||||
player.play()
|
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
|
||||||
guard let self = self else { return }
|
|
||||||
playerLayer.frame = self.videoView.bounds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func layoutSubviews() {
|
|
||||||
super.layoutSubviews()
|
|
||||||
if let playerLayer = videoView.layer.sublayers?.compactMap({ $0 as? AVPlayerLayer }).first {
|
|
||||||
playerLayer.frame = videoView.bounds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var titleLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = "有 \(Int.random(in: 1000...10000)) 人正在使用此功能"
|
|
||||||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
label.textAlignment = .center
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var marqueeScrollView: UIScrollView = {
|
|
||||||
let sv = UIScrollView()
|
|
||||||
sv.backgroundColor = .clear
|
|
||||||
sv.showsHorizontalScrollIndicator = false
|
|
||||||
sv.isScrollEnabled = false
|
|
||||||
return sv
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var searchInputView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = UIColor(hexStr: "#EFF9FF")
|
|
||||||
view.cornerRadius = 10
|
|
||||||
|
|
||||||
let titleLab = UILabel()
|
|
||||||
titleLab.text = "TA在哪?输入号码就知道"
|
|
||||||
titleLab.font = .systemFont(ofSize: 16, weight: .semibold)
|
|
||||||
titleLab.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
view.addSubview(titleLab)
|
|
||||||
titleLab.layoutChain
|
|
||||||
.top(18)
|
|
||||||
.left(15)
|
|
||||||
|
|
||||||
let inputView = UIView()
|
|
||||||
inputView.backgroundColor = .white
|
|
||||||
inputView.cornerRadius = 4
|
|
||||||
view.addSubview(inputView)
|
|
||||||
inputView.layoutChain
|
|
||||||
.topToBottomOfView(titleLab, offset: 20)
|
|
||||||
.edgesHorzontal(15)
|
|
||||||
.height(40)
|
|
||||||
|
|
||||||
let contactsBtn = UIButton()
|
|
||||||
contactsBtn.setTitle(" 通讯录导入", for: .normal)
|
|
||||||
contactsBtn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
|
|
||||||
contactsBtn.titleLabel?.font = .systemFont(ofSize: 12, weight: .regular)
|
|
||||||
contactsBtn.setImage(UIImage(named: "SearchLocation/contacts"), for: .normal)
|
|
||||||
contactsBtn.extendEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 15)
|
|
||||||
contactsBtn.rx.tap.subscribe(onNext: { _ in
|
|
||||||
//TODO: - 通讯录
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
inputView.addSubview(contactsBtn)
|
|
||||||
contactsBtn.layoutChain
|
|
||||||
.right(15)
|
|
||||||
.width(90)
|
|
||||||
.edgesVertical()
|
|
||||||
contactsBtn.sizeToFit()
|
|
||||||
|
|
||||||
inputView.addSubview(phoneInputTF)
|
|
||||||
phoneInputTF.layoutChain
|
|
||||||
.edgesVertical(10)
|
|
||||||
.left(15)
|
|
||||||
.rightToLeftOfView(contactsBtn, offset: -8)
|
|
||||||
|
|
||||||
let tipsLab = UILabel()
|
|
||||||
tipsLab.text = "我们不会存储和泄露你导入的通讯录隐私"
|
|
||||||
tipsLab.font = .systemFont(ofSize: 12, weight: .regular)
|
|
||||||
tipsLab.textColor = ThemeManager.shared.color.contentColor
|
|
||||||
view.addSubview(tipsLab)
|
|
||||||
tipsLab.layoutChain
|
|
||||||
.topToBottomOfView(inputView, offset: 6)
|
|
||||||
.right(15)
|
|
||||||
|
|
||||||
view.addSubview(searchBtn)
|
|
||||||
searchBtn.layoutChain
|
|
||||||
.topToBottomOfView(tipsLab, offset: 35)
|
|
||||||
.edgesHorzontal(52)
|
|
||||||
.height(50)
|
|
||||||
.bottom(30)
|
|
||||||
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var phoneInputTF: UITextField = {
|
|
||||||
let textField = UITextField()
|
|
||||||
textField.font = .systemFont(ofSize: 14, weight: .medium)
|
|
||||||
textField.placeholder = "请输入要查找的号码"
|
|
||||||
textField.keyboardType = .numberPad
|
|
||||||
textField.returnKeyType = .done
|
|
||||||
return textField
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var searchBtn: 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
|
|
||||||
return btn
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var searchProgressView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = .black.withAlphaComponent(0.8)
|
|
||||||
view.cornerRadius = 16
|
|
||||||
view.borderWidth = 0.5
|
|
||||||
view.borderColor = .white
|
|
||||||
view.isHidden = true
|
|
||||||
|
|
||||||
view.addSubview(searchPhoneLottieView)
|
|
||||||
searchPhoneLottieView.layoutChain
|
|
||||||
.top(30)
|
|
||||||
.left(15)
|
|
||||||
.width(120)
|
|
||||||
.heightToWidth(1)
|
|
||||||
|
|
||||||
let steps = ["分析用户号码", "正在找 CGI 蜂窝参数", "SS7 信息交流", "号码已获授权", "处理完成"]
|
|
||||||
var prevStepView: UIView?
|
|
||||||
for (i, text) in steps.enumerated() {
|
|
||||||
let stepView = UIView()
|
|
||||||
view.addSubview(stepView)
|
|
||||||
|
|
||||||
let icon = UIImageView(image: UIImage(named: "SearchLocation/done_off"))
|
|
||||||
icon.contentMode = .scaleAspectFit
|
|
||||||
stepView.addSubview(icon)
|
|
||||||
progressIcons.append(icon)
|
|
||||||
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = text
|
|
||||||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
|
||||||
label.textColor = .white
|
|
||||||
stepView.addSubview(label)
|
|
||||||
|
|
||||||
icon.layoutChain
|
|
||||||
.left().centerY()
|
|
||||||
.width(18).height(18)
|
|
||||||
|
|
||||||
label.layoutChain
|
|
||||||
.leftToRightOfView(icon, offset: 8)
|
|
||||||
.centerY().right()
|
|
||||||
|
|
||||||
stepView.layoutChain
|
|
||||||
.leftToRightOfView(searchPhoneLottieView, offset: 10)
|
|
||||||
.right(10)
|
|
||||||
.height(26)
|
|
||||||
|
|
||||||
if let prev = prevStepView {
|
|
||||||
stepView.layoutChain.topToBottomOfView(prev, offset: 4)
|
|
||||||
} else {
|
|
||||||
stepView.layoutChain.top(20)
|
|
||||||
}
|
|
||||||
prevStepView = stepView
|
|
||||||
}
|
|
||||||
|
|
||||||
// 进度条 + 百分比
|
|
||||||
progressBar.layer.cornerRadius = 4
|
|
||||||
progressBar.clipsToBounds = true
|
|
||||||
progressBar.trackTintColor = .white.withAlphaComponent(0.2)
|
|
||||||
progressBar.progressTintColor = UIColor(hexStr: "#16B3FF")
|
|
||||||
view.addSubview(progressBar)
|
|
||||||
|
|
||||||
progressLab.textColor = .white
|
|
||||||
progressLab.font = .systemFont(ofSize: 12, weight: .medium)
|
|
||||||
progressLab.textAlignment = .right
|
|
||||||
view.addSubview(progressLab)
|
|
||||||
|
|
||||||
progressBar.layoutChain
|
|
||||||
.topToBottomOfView(prevStepView!, offset: 15)
|
|
||||||
.left(20)
|
|
||||||
.height(8)
|
|
||||||
|
|
||||||
progressLab.layoutChain
|
|
||||||
.leftToRightOfView(progressBar, offset: 8)
|
|
||||||
.centerY(progressBar)
|
|
||||||
.right(20)
|
|
||||||
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
private var progressIcons: [UIImageView] = []
|
|
||||||
|
|
||||||
private lazy var progressBar: UIProgressView = {
|
|
||||||
let p = UIProgressView()
|
|
||||||
return p
|
|
||||||
}()
|
|
||||||
|
|
||||||
private lazy var progressLab: UILabel = {
|
|
||||||
let l = UILabel()
|
|
||||||
return l
|
|
||||||
}()
|
|
||||||
|
|
||||||
/// 根据视频进度更新步骤图标和进度条 (0~1)
|
|
||||||
func updateVideoProgress(_ progress: Float) {
|
|
||||||
let clamped = max(0, min(1, progress))
|
|
||||||
progressBar.progress = clamped
|
|
||||||
progressLab.text = "\(Int(clamped * 100))%"
|
|
||||||
|
|
||||||
let stepCount = progressIcons.count
|
|
||||||
let stepProgress = 1.0 / Float(stepCount)
|
|
||||||
for (i, icon) in progressIcons.enumerated() {
|
|
||||||
let stepStart = stepProgress * Float(i)
|
|
||||||
icon.image = UIImage(named: clamped >= stepStart + stepProgress * 0.5 ? "SearchLocation/done" : "SearchLocation/done_off")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var searchPhoneLottieView: LottieAnimationView = {
|
|
||||||
let view = LottieAnimationView(name: "phone_search_interlude")
|
|
||||||
view.loopMode = .loop
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: .zero)
|
|
||||||
backgroundColor = .white
|
|
||||||
setupUI()
|
|
||||||
setupRx()
|
|
||||||
setupMarquee()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -322,7 +322,7 @@ class LoginView: UIView {
|
||||||
textField.font = .systemFont(ofSize: 16, weight: .medium)
|
textField.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
textField.placeholderColor(placeholder: "请输入验证码",
|
textField.placeholderColor(placeholder: "请输入验证码",
|
||||||
color: .white)
|
color: .white)
|
||||||
textField.keyboardType = .numberPad
|
textField.keyboardType = .numbersAndPunctuation
|
||||||
return textField
|
return textField
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,7 @@ class CreateSchedulePopView: UIView {
|
||||||
tagListView.tagViews.forEach {
|
tagListView.tagViews.forEach {
|
||||||
$0.layer.cornerRadius = 4
|
$0.layer.cornerRadius = 4
|
||||||
}
|
}
|
||||||
// 强制布局使 TagListView 宽度正确 → 重新排列标签 → intrinsicContentSize 正确
|
tagListView.invalidateIntrinsicContentSize() // 通知系统重新算高
|
||||||
tagListView.setNeedsLayout()
|
|
||||||
tagListView.layoutIfNeeded()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UI
|
// MARK: - UI
|
||||||
|
|
|
||||||
|
|
@ -359,8 +359,13 @@ class CreateScheduleVC: BaseViewController, MAMapViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 缩放至包含所有点
|
// 缩放至包含所有点
|
||||||
if !pointAnnotations.isEmpty {
|
let lats = validPoints.map { $0.latitude }
|
||||||
rootView.mapView.showAnnotations(pointAnnotations, animated: true)
|
let lons = validPoints.map { $0.longitude }
|
||||||
|
if let minLat = lats.min(), let maxLat = lats.max(),
|
||||||
|
let minLon = lons.min(), let maxLon = lons.max() {
|
||||||
|
let center = CLLocationCoordinate2D(latitude: (minLat + maxLat) / 2, longitude: (minLon + maxLon) / 2)
|
||||||
|
let span = MACoordinateSpan(latitudeDelta: (maxLat - minLat) * 2.5 + 0.01, longitudeDelta: (maxLon - minLon) * 2.5 + 0.01)
|
||||||
|
rootView.mapView.setRegion(MACoordinateRegion(center: center, span: span), animated: true)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,294 +0,0 @@
|
||||||
//
|
|
||||||
// ItineraryDetailVC.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import ObjectMapper
|
|
||||||
import SwiftyUserDefaults
|
|
||||||
#if !targetEnvironment(simulator)
|
|
||||||
import AMapNaviKit
|
|
||||||
import AMapSearchKit
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class ItineraryDetailVC: BaseViewController {
|
|
||||||
|
|
||||||
fileprivate var rootView: ItineraryDetailView!
|
|
||||||
private var points: [SchedulePointModel] = []
|
|
||||||
private let routeSearch = AMapSearchAPI()
|
|
||||||
private var routeOverlays: [MAPolyline] = []
|
|
||||||
private var pointAnnotations: [MAPointAnnotation] = []
|
|
||||||
|
|
||||||
init(scheduleJson: [String: Any]) {
|
|
||||||
let model = ScheduleModel(JSON: scheduleJson)
|
|
||||||
self.points = model?.points ?? []
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override func loadView() {
|
|
||||||
rootView = ItineraryDetailView(frame: UIScreen.main.bounds)
|
|
||||||
view = rootView
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
setupMap()
|
|
||||||
addPointAnnotations()
|
|
||||||
requestRoute()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidDisappear(_ animated: Bool) {
|
|
||||||
super.viewDidDisappear(animated)
|
|
||||||
if isMovingFromParent || isBeingDismissed {
|
|
||||||
rootView.cleanupMap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Map
|
|
||||||
|
|
||||||
private func setupMap() {
|
|
||||||
#if !targetEnvironment(simulator)
|
|
||||||
rootView.mapView.delegate = self
|
|
||||||
rootView.mapView.showsUserLocation = false
|
|
||||||
routeSearch?.delegate = self
|
|
||||||
if let lat = Defaults[\.currentLatitude], let lon = Defaults[\.currentLongitude] {
|
|
||||||
let coord = CLLocationCoordinate2D(latitude: lat, longitude: lon)
|
|
||||||
if CLLocationCoordinate2DIsValid(coord) {
|
|
||||||
rootView.mapView.setCenter(coord, animated: false)
|
|
||||||
rootView.mapView.setZoomLevel(14, animated: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private func addPointAnnotations() {
|
|
||||||
#if !targetEnvironment(simulator)
|
|
||||||
for ann in pointAnnotations { rootView.mapView.removeAnnotation(ann) }
|
|
||||||
pointAnnotations.removeAll()
|
|
||||||
|
|
||||||
// lat/lon 都为 0 的视为无效坐标,跳过
|
|
||||||
let validPoints = points.filter {
|
|
||||||
guard let lat = $0.latitude, let lon = $0.longitude else { return false }
|
|
||||||
return abs(lat) > 0.0001 && abs(lon) > 0.0001
|
|
||||||
}
|
|
||||||
for (i, p) in validPoints.enumerated() {
|
|
||||||
let ann = MAPointAnnotation()
|
|
||||||
ann.coordinate = CLLocationCoordinate2D(latitude: p.latitude!, longitude: p.longitude!)
|
|
||||||
ann.title = "\(i + 1)"
|
|
||||||
let timeStr = view.getDateInterval2String(date: "\(p.expected_timestamp / 1000)", dateFormat: "yyyy-MM-dd HH:mm")
|
|
||||||
ann.subtitle = "\(p.street)|\(timeStr)"
|
|
||||||
rootView.mapView.addAnnotation(ann)
|
|
||||||
pointAnnotations.append(ann)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缩放到包含所有标注点
|
|
||||||
if !pointAnnotations.isEmpty {
|
|
||||||
rootView.mapView.showAnnotations(pointAnnotations, animated: true)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private func requestRoute() {
|
|
||||||
#if !targetEnvironment(simulator)
|
|
||||||
let validPoints = points.filter { ($0.latitude ?? 0) != 0 || ($0.longitude ?? 0) != 0 }
|
|
||||||
guard validPoints.count >= 2 else { return }
|
|
||||||
|
|
||||||
let request = AMapDrivingRouteSearchRequest()
|
|
||||||
request.origin = AMapGeoPoint.location(withLatitude: CGFloat(validPoints[0].latitude ?? 0),
|
|
||||||
longitude: CGFloat(validPoints[0].longitude ?? 0))
|
|
||||||
request.destination = AMapGeoPoint.location(withLatitude: CGFloat(validPoints.last?.latitude ?? 0),
|
|
||||||
longitude: CGFloat(validPoints.last?.longitude ?? 0))
|
|
||||||
if validPoints.count > 2 {
|
|
||||||
var waypoints: [AMapGeoPoint] = []
|
|
||||||
for i in 1..<validPoints.count - 1 {
|
|
||||||
let p = validPoints[i]
|
|
||||||
if let wp = AMapGeoPoint.location(withLatitude: CGFloat(p.latitude ?? 0),
|
|
||||||
longitude: CGFloat(p.longitude ?? 0)) {
|
|
||||||
waypoints.append(wp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request.waypoints = waypoints
|
|
||||||
}
|
|
||||||
request.strategy = 0
|
|
||||||
routeSearch?.aMapDrivingRouteSearch(request)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 生成数字图标
|
|
||||||
private static func numberImage(_ num: Int) -> UIImage? {
|
|
||||||
let size = CGSize(width: 28, height: 28)
|
|
||||||
let rect = CGRect(origin: .zero, size: size)
|
|
||||||
UIGraphicsBeginImageContextWithOptions(size, false, 0)
|
|
||||||
guard let ctx = UIGraphicsGetCurrentContext() else { return nil }
|
|
||||||
ctx.setLineWidth(1)
|
|
||||||
ctx.setStrokeColor(UIColor.white.cgColor)
|
|
||||||
ctx.setFillColor(UIColor(hexStr: "#16B3FF").cgColor)
|
|
||||||
let path = UIBezierPath(ovalIn: rect)
|
|
||||||
path.fill()
|
|
||||||
path.stroke()
|
|
||||||
let text = "\(num)" as NSString
|
|
||||||
let attrs: [NSAttributedString.Key: Any] = [.font: UIFont.boldSystemFont(ofSize: 13), .foregroundColor: UIColor.white]
|
|
||||||
let strSize = text.size(withAttributes: attrs)
|
|
||||||
text.draw(at: CGPoint(x: (size.width - strSize.width) / 2, y: (size.height - strSize.height) / 2))
|
|
||||||
let img = UIGraphicsGetImageFromCurrentImageContext()
|
|
||||||
UIGraphicsEndImageContext()
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 生成 callout 背景图
|
|
||||||
private static func calloutImage() -> UIImage? {
|
|
||||||
let size = CGSize(width: 200, height: 50)
|
|
||||||
UIGraphicsBeginImageContextWithOptions(size, false, 0)
|
|
||||||
guard let ctx = UIGraphicsGetCurrentContext() else { return nil }
|
|
||||||
ctx.setFillColor(UIColor.white.cgColor)
|
|
||||||
let path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 8)
|
|
||||||
path.fill()
|
|
||||||
ctx.setStrokeColor(UIColor(hexStr: "#16B3FF").cgColor)
|
|
||||||
ctx.setLineWidth(1)
|
|
||||||
path.stroke()
|
|
||||||
let img = UIGraphicsGetImageFromCurrentImageContext()
|
|
||||||
UIGraphicsEndImageContext()
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !targetEnvironment(simulator)
|
|
||||||
// MARK: - MAMapViewDelegate
|
|
||||||
extension ItineraryDetailVC: MAMapViewDelegate {
|
|
||||||
func mapView(_ mapView: MAMapView!, viewFor annotation: MAAnnotation!) -> MAAnnotationView! {
|
|
||||||
guard !(annotation is MAUserLocation) else { return nil }
|
|
||||||
guard let pointAnn = annotation as? MAPointAnnotation else { return nil }
|
|
||||||
|
|
||||||
// 行程点标注
|
|
||||||
if let num = Int(pointAnn.title ?? "") {
|
|
||||||
let id = "ItineraryPin"
|
|
||||||
var view = mapView.dequeueReusableAnnotationView(withIdentifier: id)
|
|
||||||
if view == nil {
|
|
||||||
view = MAAnnotationView(annotation: annotation, reuseIdentifier: id)
|
|
||||||
} else {
|
|
||||||
view?.annotation = annotation
|
|
||||||
}
|
|
||||||
view?.image = Self.numberImage(num)
|
|
||||||
view?.centerOffset = CGPoint(x: 0, y: -14)
|
|
||||||
|
|
||||||
// callout
|
|
||||||
let callout = CalloutView(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
|
|
||||||
if let subtitle = pointAnn.subtitle {
|
|
||||||
let parts = subtitle.components(separatedBy: "|")
|
|
||||||
if parts.count == 2 {
|
|
||||||
callout.nameLab.text = parts[0]
|
|
||||||
callout.timeLab.text = parts[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
view?.customCalloutView = callout
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapView(_ mapView: MAMapView!, didSelect view: MAAnnotationView!) {
|
|
||||||
// 显示自定义 callout
|
|
||||||
if let customCallout = view.customCalloutView {
|
|
||||||
view.addSubview(customCallout)
|
|
||||||
customCallout.frame = CGRect(x: (view.bounds.width - 200) / 2, y: -55, width: 200, height: 50)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapView(_ mapView: MAMapView!, didDeselect view: MAAnnotationView!) {
|
|
||||||
view.customCalloutView?.removeFromSuperview()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - AMapSearchDelegate
|
|
||||||
extension ItineraryDetailVC: AMapSearchDelegate {
|
|
||||||
func onRouteSearchDone(_ request: AMapRouteSearchBaseRequest!, response: AMapRouteSearchResponse!) {
|
|
||||||
guard let path = response.route?.paths?.first as? AMapPath else { return }
|
|
||||||
var coords: [CLLocationCoordinate2D] = []
|
|
||||||
for step in path.steps {
|
|
||||||
guard let polylineStr = step.polyline else { continue }
|
|
||||||
for point in polylineStr.components(separatedBy: ";") {
|
|
||||||
let latLon = point.components(separatedBy: ",")
|
|
||||||
if latLon.count == 2, let lon = Double(latLon[0]), let lat = Double(latLon[1]) {
|
|
||||||
coords.append(CLLocationCoordinate2D(latitude: lat, longitude: lon))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
guard coords.count > 1 else { return }
|
|
||||||
var mutableCoords = coords
|
|
||||||
if let polyline = MAPolyline(coordinates: &mutableCoords, count: UInt(coords.count)) {
|
|
||||||
rootView.mapView.add(polyline)
|
|
||||||
routeOverlays.append(polyline)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func aMapSearchRequest(_ request: Any!, didFailWithError error: Error!) {
|
|
||||||
print("Route search error: \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapView(_ mapView: MAMapView!, rendererFor overlay: MAOverlay!) -> MAOverlayRenderer! {
|
|
||||||
if let polyline = overlay as? MAPolyline {
|
|
||||||
let r = MAPolylineRenderer(polyline: polyline)
|
|
||||||
r?.strokeColor = UIColor(hexStr: "#16B3FF")
|
|
||||||
r?.lineWidth = 4
|
|
||||||
r?.lineDashType = kMALineDashTypeSquare
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - MAAnnotationView + customCalloutView
|
|
||||||
private var calloutViewKey: UInt8 = 0
|
|
||||||
extension MAAnnotationView {
|
|
||||||
var customCalloutView: UIView? {
|
|
||||||
get { objc_getAssociatedObject(self, &calloutViewKey) as? UIView }
|
|
||||||
set { objc_setAssociatedObject(self, &calloutViewKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - CalloutView
|
|
||||||
class CalloutView: UIView {
|
|
||||||
let nameLab: UILabel = {
|
|
||||||
let l = UILabel()
|
|
||||||
l.font = .systemFont(ofSize: 13, weight: .medium)
|
|
||||||
l.textColor = UIColor(hexStr: "#333333")
|
|
||||||
l.textAlignment = .center
|
|
||||||
return l
|
|
||||||
}()
|
|
||||||
|
|
||||||
let timeLab: UILabel = {
|
|
||||||
let l = UILabel()
|
|
||||||
l.font = .systemFont(ofSize: 11, weight: .regular)
|
|
||||||
l.textColor = UIColor(hexStr: "#999999")
|
|
||||||
l.textAlignment = .center
|
|
||||||
return l
|
|
||||||
}()
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: frame)
|
|
||||||
backgroundColor = .white
|
|
||||||
layer.cornerRadius = 8
|
|
||||||
layer.shadowColor = UIColor.black.withAlphaComponent(0.15).cgColor
|
|
||||||
layer.shadowOffset = .zero
|
|
||||||
layer.shadowRadius = 4
|
|
||||||
layer.shadowOpacity = 1
|
|
||||||
|
|
||||||
addSubview(nameLab)
|
|
||||||
addSubview(timeLab)
|
|
||||||
nameLab.layoutChain.top(8).centerX().edgesHorzontal(10)
|
|
||||||
timeLab.layoutChain.topToBottomOfView(nameLab, offset: 4).centerX().edgesHorzontal(10).bottom(8)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
//
|
|
||||||
// ItineraryDetailView.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import AMapNaviKit
|
|
||||||
|
|
||||||
class ItineraryDetailView: UIView {
|
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
|
||||||
|
|
||||||
private func setupRx() {
|
|
||||||
backBtn.rx.tap.subscribe(onNext: { _ in
|
|
||||||
AppRouter.shared.popOrDismiss()
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupUI() {
|
|
||||||
addSubview(mapView)
|
|
||||||
addSubview(navBgView)
|
|
||||||
addSubview(navBarView)
|
|
||||||
navBarView.addSubview(navTitleLabel)
|
|
||||||
navBarView.addSubview(backBtn)
|
|
||||||
|
|
||||||
navBgView.layoutChain
|
|
||||||
.edges(excludingEdge: .bottom)
|
|
||||||
.heightToWidth(160/375)
|
|
||||||
|
|
||||||
navBarView.layoutChain
|
|
||||||
.edges(excludingEdge: .bottom)
|
|
||||||
.height(kNaviHeight)
|
|
||||||
|
|
||||||
navTitleLabel.layoutChain
|
|
||||||
.top(kStatusBarHeight + 12)
|
|
||||||
.centerY(backBtn)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
backBtn.layoutChain
|
|
||||||
.centerY(navTitleLabel)
|
|
||||||
.left(15)
|
|
||||||
.width(24)
|
|
||||||
.height(24)
|
|
||||||
|
|
||||||
mapView.layoutChain
|
|
||||||
.edges()
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var navBgView: UIImageView = {
|
|
||||||
let iv = UIImageView()
|
|
||||||
iv.image = UIImage(named: "Common/navBar_bg_2")
|
|
||||||
iv.contentMode = .scaleAspectFill
|
|
||||||
return iv
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var navBarView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = .clear
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var navTitleLabel: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = "行程路线"
|
|
||||||
label.font = .systemFont(ofSize: 18, weight: .medium)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
label.textAlignment = .center
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var backBtn: UIButton = {
|
|
||||||
let btn = UIButton(type: .custom)
|
|
||||||
btn.setImage(UIImage(named: "Common/back"), for: .normal)
|
|
||||||
btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100)
|
|
||||||
return btn
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var mapView: MAMapView! = {
|
|
||||||
let mv = MAMapView()
|
|
||||||
mv.zoomLevel = 14
|
|
||||||
mv.showsUserLocation = false
|
|
||||||
mv.showsCompass = false
|
|
||||||
mv.userTrackingMode = .none
|
|
||||||
return mv
|
|
||||||
}()
|
|
||||||
|
|
||||||
func cleanupMap() {
|
|
||||||
mapView?.delegate = nil
|
|
||||||
mapView?.removeFromSuperview()
|
|
||||||
mapView = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: .zero)
|
|
||||||
backgroundColor = .white
|
|
||||||
setupUI()
|
|
||||||
setupRx()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -28,38 +28,11 @@ class ScheduleDetailVC: BaseViewController {
|
||||||
// Do any additional setup after loading the view.
|
// Do any additional setup after loading the view.
|
||||||
setupData()
|
setupData()
|
||||||
bindViewModel()
|
bindViewModel()
|
||||||
reactiveAction()
|
|
||||||
requestFollowList()
|
requestFollowList()
|
||||||
|
|
||||||
viewModel.loadPointData()
|
viewModel.loadPointData()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
|
||||||
super.viewWillAppear(animated)
|
|
||||||
rootView.vipTipsLab.text = AppContextManager.shared.vip > 1 ? "" : "升级 VIP,可查看具体人员与节点"
|
|
||||||
}
|
|
||||||
|
|
||||||
private func reactiveAction() {
|
|
||||||
rootView.operateBtn.rx.tap.subscribe(onNext: { _ in
|
|
||||||
guard let model = self.viewModel.scheduModel else { return }
|
|
||||||
if model.is_own {
|
|
||||||
AppRouter.push(Route.createSchedule, userInfo: ["scheduleJson": model.toJSON()])
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
self.requestSetFollow(id: model.id, op: self.rootView.operateBtn.isSelected)
|
|
||||||
}
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
|
|
||||||
rootView.routeBtn.rx.tap.subscribe(onNext: { _ in
|
|
||||||
guard let model = self.viewModel.scheduModel else { return }
|
|
||||||
AppRouter.push(Route.itineraryDetail, userInfo: ["scheduleJson": model.toJSON()])
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
|
|
||||||
rootView.vipTipsLab.rx.tapGesture.subscribe(onNext: { _ in
|
|
||||||
AppRouter.push(Route.vipRecharge)
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func bindViewModel() {
|
private func bindViewModel() {
|
||||||
viewModel.output.sectionedItems
|
viewModel.output.sectionedItems
|
||||||
.bind(to: rootView.collectionView.rx.items(dataSource: dataSource))
|
.bind(to: rootView.collectionView.rx.items(dataSource: dataSource))
|
||||||
|
|
@ -74,37 +47,18 @@ class ScheduleDetailVC: BaseViewController {
|
||||||
guard let model = viewModel.scheduModel else { return }
|
guard let model = viewModel.scheduModel else { return }
|
||||||
rootView.dateLab.text = rootView.dateLab.getDateInterval2String(date: "\(model.timestamp / 1000)", dateFormat: "yyyy年MM月dd日")
|
rootView.dateLab.text = rootView.dateLab.getDateInterval2String(date: "\(model.timestamp / 1000)", dateFormat: "yyyy年MM月dd日")
|
||||||
rootView.creatorIcon.image = model.userIcon
|
rootView.creatorIcon.image = model.userIcon
|
||||||
|
|
||||||
var operateName = ""
|
|
||||||
if model.is_own {
|
|
||||||
operateName = "编辑行程"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
operateName = "关注行程"
|
|
||||||
rootView.operateBtn.isSelected = model.is_follow
|
|
||||||
}
|
|
||||||
rootView.operateBtn.setTitle(operateName, for: .normal)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - API
|
// MARK: - API
|
||||||
private func requestFollowList() {
|
private func requestFollowList() {
|
||||||
DLToast.showLoading()
|
dl.showLoading()
|
||||||
ItineraryService.queryFollowList(id: viewModel.routeId).subscribe(onNext: { response in
|
UserService.followList().subscribe(onNext: { response in
|
||||||
DLToast.dismiss()
|
self.dl.dismiss()
|
||||||
self.viewModel.loadViewedData(response.list)
|
self.viewModel.loadViewedData(response.list)
|
||||||
self.rootView.noDataLab.isHidden = response.list.count > 0
|
self.rootView.noDataLab.isHidden = response.list.count > 0
|
||||||
}, onError: { _ in }).disposed(by: disposeBag)
|
}, onError: { _ in }).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.rootView.operateBtn.isSelected = !op
|
|
||||||
}, onError: { _ in }).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - dataSource
|
// MARK: - dataSource
|
||||||
private lazy var dataSource: RxCollectionViewSectionedReloadDataSource<ViewedListSectionModel> = {
|
private lazy var dataSource: RxCollectionViewSectionedReloadDataSource<ViewedListSectionModel> = {
|
||||||
RxCollectionViewSectionedReloadDataSource<ViewedListSectionModel> { datasource, collectionView, indexPath, model in
|
RxCollectionViewSectionedReloadDataSource<ViewedListSectionModel> { datasource, collectionView, indexPath, model in
|
||||||
|
|
|
||||||
|
|
@ -254,54 +254,11 @@ class ScheduleDetailView: UIView {
|
||||||
v.layer.shadowOffset = CGSize(width: 0, height: -2)
|
v.layer.shadowOffset = CGSize(width: 0, height: -2)
|
||||||
v.layer.shadowRadius = 10
|
v.layer.shadowRadius = 10
|
||||||
v.layer.shadowOpacity = 1
|
v.layer.shadowOpacity = 1
|
||||||
|
|
||||||
forwardBtn.isHidden = true
|
|
||||||
v.addSubview(forwardBtn)
|
|
||||||
v.addSubview(operateBtn)
|
|
||||||
v.addSubview(routeBtn)
|
|
||||||
|
|
||||||
forwardBtn.layoutChain
|
|
||||||
.centerY()
|
|
||||||
.left(15)
|
|
||||||
.width(80)
|
|
||||||
|
|
||||||
routeBtn.layoutChain
|
|
||||||
.centerY()
|
|
||||||
.right(15)
|
|
||||||
.width(80)
|
|
||||||
|
|
||||||
operateBtn.layoutChain
|
|
||||||
.centerY()
|
|
||||||
.leftToRightOfView(forwardBtn, offset: 20)
|
|
||||||
.rightToLeftOfView(routeBtn, offset: -20)
|
|
||||||
.height(50)
|
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var forwardBtn: UIButton = makeVerticalButton(image: UIImage(named: "Schedule/forward"), title: "发送到圈子")
|
|
||||||
lazy var routeBtn: UIButton = makeVerticalButton(image: UIImage(named: "Schedule/route"), title: "查看路线")
|
|
||||||
|
|
||||||
/// 创建图片在上、文字在下的按钮
|
|
||||||
private func makeVerticalButton(image: UIImage?, title: String) -> UIButton {
|
|
||||||
let btn = UIButton(type: .custom)
|
|
||||||
btn.setImage(image, for: .normal)
|
|
||||||
btn.setTitle(title, for: .normal)
|
|
||||||
btn.setTitleColor(UIColor(hexStr: "#333333"), for: .normal)
|
|
||||||
btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
|
||||||
btn.titleLabel?.textAlignment = .center
|
|
||||||
btn.imageView?.contentMode = .scaleAspectFit
|
|
||||||
// 图片在上、文字在下
|
|
||||||
let width = btn.titleLabel?.intrinsicContentSize.width ?? 0
|
|
||||||
btn.imageEdgeInsets = UIEdgeInsets(top: -20, left: 0, bottom: 0, right: -width)
|
|
||||||
btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: -24, bottom: -24, right: 0)
|
|
||||||
btn.contentEdgeInsets = UIEdgeInsets(top: 24, left: 0, bottom: 24, right: 0)
|
|
||||||
return btn
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var operateBtn: UIButton = {
|
lazy var operateBtn: UIButton = {
|
||||||
let btn = UIButton()
|
let btn = UIButton()
|
||||||
btn.setTitle("取消关注", for: .selected)
|
|
||||||
btn.setTitleColor(.white, for: .normal)
|
btn.setTitleColor(.white, for: .normal)
|
||||||
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||||||
|
|
@ -314,6 +271,8 @@ class ScheduleDetailView: UIView {
|
||||||
backgroundColor = .white
|
backgroundColor = .white
|
||||||
setupUI()
|
setupUI()
|
||||||
setupRx()
|
setupRx()
|
||||||
|
|
||||||
|
vipTipsLab.text = AppContextManager.shared.vip > 1 ? "" : "升级 VIP,查看具体事件"
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
//
|
|
||||||
// ScheduleHistoryVC.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import RxDataSources
|
|
||||||
import MJRefresh
|
|
||||||
|
|
||||||
class ScheduleHistoryVC: BaseViewController {
|
|
||||||
|
|
||||||
fileprivate var rootView: ScheduleHistoryView!
|
|
||||||
|
|
||||||
override func loadView() {
|
|
||||||
rootView = ScheduleHistoryView(frame: UIScreen.main.bounds)
|
|
||||||
view = rootView
|
|
||||||
}
|
|
||||||
|
|
||||||
private var viewModel = ScheduleHistoryViewModel()
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
// Do any additional setup after loading the view.
|
|
||||||
bindViewModel()
|
|
||||||
requestData()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
|
||||||
super.viewWillAppear(animated)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func requestData() {
|
|
||||||
dl.showLoading()
|
|
||||||
viewModel.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func bindViewModel() {
|
|
||||||
viewModel.output.sectionedItems
|
|
||||||
.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)
|
|
||||||
|
|
||||||
rootView.tableView.rx.modelSelected(ScheduleModel.self)
|
|
||||||
.subscribe(viewModel.cellAction.inputs)
|
|
||||||
.disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - dataSource
|
|
||||||
lazy private var tableViewDataSource: RxTableViewSectionedReloadDataSource<ScheduleListSectionModel> = {
|
|
||||||
return RxTableViewSectionedReloadDataSource<ScheduleListSectionModel>(
|
|
||||||
configureCell: { (_, tableView, indexPath, model) in
|
|
||||||
let cell: ScheduleHistoryListCell = tableView.dequeueReusableCell(for: indexPath)
|
|
||||||
cell.configure(model)
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
//
|
|
||||||
// ScheduleHistoryVM.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import RxDataSources
|
|
||||||
import MJRefresh
|
|
||||||
import ObjectMapper
|
|
||||||
|
|
||||||
class ScheduleHistoryViewModel: ViewModelType {
|
|
||||||
|
|
||||||
struct Input {}
|
|
||||||
|
|
||||||
struct Output {
|
|
||||||
var sectionedItems: Observable<[ScheduleListSectionModel]>
|
|
||||||
var refreshResult: Observable<RefreshResult>
|
|
||||||
var pagination: Observable<PaginationModel?>
|
|
||||||
var error: Observable<Error>
|
|
||||||
}
|
|
||||||
|
|
||||||
let input: Input
|
|
||||||
let output: Output
|
|
||||||
|
|
||||||
private var listService: ListService<ScheduleListResponse>
|
|
||||||
private let sectionedItems = PublishSubject<[ScheduleListSectionModel]>()
|
|
||||||
|
|
||||||
var refresh: MJRefreshComponentAction {
|
|
||||||
return {
|
|
||||||
self.listService.request.onNext(.refresh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var loadMore: MJRefreshComponentAction {
|
|
||||||
return {
|
|
||||||
self.listService.request.onNext(.more)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var cellAction: Action<ScheduleModel, Void> = { this in
|
|
||||||
return Action { model in
|
|
||||||
AppRouter.push(Route.itineraryDetail, userInfo: ["scheduleJson": model.toJSON()])
|
|
||||||
return .empty()
|
|
||||||
}
|
|
||||||
}(self)
|
|
||||||
|
|
||||||
// MARK: - init
|
|
||||||
init() {
|
|
||||||
listService = ItineraryService.query(history: true)
|
|
||||||
|
|
||||||
input = Input()
|
|
||||||
output = Output(
|
|
||||||
sectionedItems: listService.animatableSectionedItems,
|
|
||||||
refreshResult: listService.refreshResult,
|
|
||||||
pagination: listService.pagination,
|
|
||||||
error: listService.error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,241 +0,0 @@
|
||||||
//
|
|
||||||
// ScheduleHistoryView.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import TagListView
|
|
||||||
|
|
||||||
class ScheduleHistoryView: UIView {
|
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
|
||||||
|
|
||||||
private func setupRx() {
|
|
||||||
backBtn.rx.tap.subscribe(onNext: { _ in
|
|
||||||
AppRouter.shared.popOrDismiss()
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupUI() {
|
|
||||||
addSubview(navBgView)
|
|
||||||
addSubview(navBarView)
|
|
||||||
navBarView.addSubview(navTitleLabel)
|
|
||||||
navBarView.addSubview(backBtn)
|
|
||||||
addSubview(tableView)
|
|
||||||
|
|
||||||
navBgView.layoutChain
|
|
||||||
.edges(excludingEdge: .bottom)
|
|
||||||
.heightToWidth(160/375)
|
|
||||||
|
|
||||||
navBarView.layoutChain
|
|
||||||
.edges(excludingEdge: .bottom)
|
|
||||||
.height(kNaviHeight)
|
|
||||||
|
|
||||||
navTitleLabel.layoutChain
|
|
||||||
.top(kStatusBarHeight + 12)
|
|
||||||
.centerY(backBtn)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
backBtn.layoutChain
|
|
||||||
.centerY(navTitleLabel)
|
|
||||||
.left(15)
|
|
||||||
.width(24)
|
|
||||||
.height(24)
|
|
||||||
|
|
||||||
tableView.layoutChain
|
|
||||||
.topToBottomOfView(navBarView, offset: 15)
|
|
||||||
.edges(excludingEdge: .top)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var navBgView: UIImageView = {
|
|
||||||
let iv = UIImageView()
|
|
||||||
iv.image = UIImage(named: "Common/navBar_bg_2")
|
|
||||||
iv.contentMode = .scaleAspectFill
|
|
||||||
return iv
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var navBarView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = .clear
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var navTitleLabel: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = "历史行程"
|
|
||||||
label.font = .systemFont(ofSize: 18, weight: .medium)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
label.textAlignment = .center
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var backBtn: UIButton = {
|
|
||||||
let btn = UIButton(type: .custom)
|
|
||||||
btn.setImage(UIImage(named: "Common/back"), for: .normal)
|
|
||||||
btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100)
|
|
||||||
return btn
|
|
||||||
}()
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: .zero)
|
|
||||||
backgroundColor = .white
|
|
||||||
setupUI()
|
|
||||||
setupRx()
|
|
||||||
}
|
|
||||||
|
|
||||||
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(ScheduleHistoryListCell.self)
|
|
||||||
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: kSafeBottomMargin + 20, right: 0)
|
|
||||||
return tableView
|
|
||||||
}()
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - ScheduleHistoryListCell
|
|
||||||
class ScheduleHistoryListCell: UITableViewCell {
|
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
|
||||||
|
|
||||||
func configure(_ model: ScheduleModel) {
|
|
||||||
nameLab.text = model.nick_name + " 的行程路线"
|
|
||||||
monthLab.text = getDateInterval2String(date: "\(model.timestamp/1000)", dateFormat: "MM月")
|
|
||||||
|
|
||||||
let groupNames = model.groups.map { $0.group_name }
|
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
nameLab.layoutChain
|
|
||||||
.top(15)
|
|
||||||
.leftToRightOfView(monthLab, offset: 15)
|
|
||||||
|
|
||||||
tagListView.layoutChain
|
|
||||||
.leftToRightOfView(dateLab, offset: 15)
|
|
||||||
.bottom(15)
|
|
||||||
.right(15)
|
|
||||||
|
|
||||||
nameLab.layoutChain.bottomToTopOfView(tagListView, offset:-5)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
@ -65,7 +65,7 @@ struct ScheduleModel: Mappable, Equatable {
|
||||||
/// 行程点
|
/// 行程点
|
||||||
var points: [SchedulePointModel] = []
|
var points: [SchedulePointModel] = []
|
||||||
/// 圈子
|
/// 圈子
|
||||||
var groups: [GroupCommonModel] = []
|
var groups: [ScheduleGroupModel] = []
|
||||||
|
|
||||||
init?(map: Map) {
|
init?(map: Map) {
|
||||||
|
|
||||||
|
|
@ -205,7 +205,7 @@ extension SchedulePointEventModel: IdentifiableType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 行程分享的圈子
|
/// 行程分享的圈子
|
||||||
struct GroupCommonModel: Mappable, Equatable {
|
struct ScheduleGroupModel: Mappable, Equatable {
|
||||||
var uuid: String = UUID().uuidString
|
var uuid: String = UUID().uuidString
|
||||||
/// id
|
/// id
|
||||||
var group_key: String = ""
|
var group_key: String = ""
|
||||||
|
|
@ -222,7 +222,7 @@ struct GroupCommonModel: Mappable, Equatable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension GroupCommonModel: IdentifiableType {
|
extension ScheduleGroupModel: IdentifiableType {
|
||||||
public typealias Identity = String
|
public typealias Identity = String
|
||||||
|
|
||||||
public var identity: String {
|
public var identity: String {
|
||||||
|
|
|
||||||
|
|
@ -27,16 +27,15 @@ class ScheduleVC: BaseViewController {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
// Do any additional setup after loading the view.
|
// Do any additional setup after loading the view.
|
||||||
rootView.allCheckBtn.isSelected = true
|
|
||||||
setupTableViewHeaderFooter()
|
setupTableViewHeaderFooter()
|
||||||
bindViewModel()
|
bindViewModel()
|
||||||
reactiveAction()
|
reactiveAction()
|
||||||
|
|
||||||
|
requestFollowList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
requestFollowList()
|
|
||||||
requestData()
|
requestData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,42 +54,13 @@ class ScheduleVC: BaseViewController {
|
||||||
self.rootView.tableView.refresh(status: status, isEmpty: isEmpty)
|
self.rootView.tableView.refresh(status: status, isEmpty: isEmpty)
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
rootView.collectionView.rx.modelSelected(ViewedModel.self)
|
|
||||||
.subscribe(viewModel.viewedCellAction.inputs)
|
|
||||||
.disposed(by: disposeBag)
|
|
||||||
|
|
||||||
rootView.tableView.rx.modelSelected(ScheduleModel.self)
|
rootView.tableView.rx.modelSelected(ScheduleModel.self)
|
||||||
.subscribe(viewModel.cellAction.inputs)
|
.subscribe(viewModel.cellAction.inputs)
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reactiveAction() {
|
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() {
|
private func setupTableViewHeaderFooter() {
|
||||||
|
|
@ -109,33 +79,19 @@ class ScheduleVC: BaseViewController {
|
||||||
|
|
||||||
// MARK: - API
|
// MARK: - API
|
||||||
private func requestFollowList() {
|
private func requestFollowList() {
|
||||||
DLToast.showLoading()
|
dl.showLoading()
|
||||||
UserService.followList().subscribe(onNext: { response in
|
UserService.followList().subscribe(onNext: { response in
|
||||||
DLToast.dismiss()
|
self.dl.dismiss()
|
||||||
self.viewModel.loadViewedData(response.list)
|
self.viewModel.loadViewedData(response.list)
|
||||||
}).disposed(by: disposeBag)
|
}, onError: { _ in }).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 行程列表
|
// MARK: - API 行程列表
|
||||||
private func requestData() {
|
private func requestData() {
|
||||||
|
dl.showLoading()
|
||||||
viewModel.refresh()
|
viewModel.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更换查询条件
|
|
||||||
private func changeCondition(follow: Bool, own: Bool) {
|
|
||||||
viewModel.changeCondition(follow: follow, own: own)
|
|
||||||
requestData()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - dataSource
|
// MARK: - dataSource
|
||||||
private lazy var dataSource: RxCollectionViewSectionedReloadDataSource<ViewedListSectionModel> = {
|
private lazy var dataSource: RxCollectionViewSectionedReloadDataSource<ViewedListSectionModel> = {
|
||||||
RxCollectionViewSectionedReloadDataSource<ViewedListSectionModel> { datasource, collectionView, indexPath, model in
|
RxCollectionViewSectionedReloadDataSource<ViewedListSectionModel> { datasource, collectionView, indexPath, model in
|
||||||
|
|
@ -159,7 +115,7 @@ class ScheduleVC: BaseViewController {
|
||||||
|
|
||||||
// 关注
|
// 关注
|
||||||
cell.followBtn.rx.tap.subscribe(onNext: { _ in
|
cell.followBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
self.requestSetFollow(id: model.id, op: cell.followBtn.isSelected)
|
|
||||||
}).disposed(by: cell.disposeBag)
|
}).disposed(by: cell.disposeBag)
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import RxSwift
|
import RxSwift
|
||||||
import RxCocoa
|
import RxCocoa
|
||||||
import TagListView
|
|
||||||
|
|
||||||
class ScheduleView: UIView {
|
class ScheduleView: UIView {
|
||||||
|
|
||||||
|
|
@ -113,9 +112,6 @@ class ScheduleView: UIView {
|
||||||
historyBtn.setTitle("历史行程", for: .normal)
|
historyBtn.setTitle("历史行程", for: .normal)
|
||||||
historyBtn.setTitleColor(ThemeManager.shared.color.titleAuxColor, for: .normal)
|
historyBtn.setTitleColor(ThemeManager.shared.color.titleAuxColor, for: .normal)
|
||||||
historyBtn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
historyBtn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
historyBtn.rx.tap.subscribe(onNext: { _ in
|
|
||||||
AppRouter.push(Route.scheduleHistory)
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
view.addSubview(historyBtn)
|
view.addSubview(historyBtn)
|
||||||
historyBtn.layoutChain
|
historyBtn.layoutChain
|
||||||
.right(15)
|
.right(15)
|
||||||
|
|
@ -171,45 +167,14 @@ class ScheduleView: UIView {
|
||||||
|
|
||||||
titleLab.layoutChain.leftToRightOfView(dotView, offset: 5)
|
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
|
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 = {
|
lazy var tableView: UITableView = {
|
||||||
let tableView = UITableView(frame: .zero, style: .plain)
|
let tableView = UITableView(frame: .zero, style: .plain)
|
||||||
tableView.backgroundColor = .clear
|
tableView.backgroundColor = .clear
|
||||||
tableView.separatorStyle = .none
|
tableView.separatorStyle = .none
|
||||||
tableView.estimatedRowHeight = 80
|
tableView.estimatedRowHeight = 80
|
||||||
tableView.rowHeight = UITableView.automaticDimension
|
|
||||||
tableView.showsVerticalScrollIndicator = false
|
tableView.showsVerticalScrollIndicator = false
|
||||||
tableView.register(ScheduleListPopCell.self)
|
tableView.register(ScheduleListPopCell.self)
|
||||||
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
|
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
|
||||||
|
|
@ -318,22 +283,15 @@ class ScheduleListPopCell: UITableViewCell {
|
||||||
func configure(_ model: ScheduleModel) {
|
func configure(_ model: ScheduleModel) {
|
||||||
nameLab.text = model.nick_name + " 的行程路线"
|
nameLab.text = model.nick_name + " 的行程路线"
|
||||||
monthLab.text = getDateInterval2String(date: "\(model.timestamp/1000)", dateFormat: "MM月")
|
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 }
|
if model.is_own {
|
||||||
// addTags 内部立即用 frame.width 排标签,先给个预估宽度避免算错
|
editBtn.isHidden = false
|
||||||
if !groupNames.isEmpty {
|
followBtn.isHidden = true
|
||||||
let w = contentView.frame.width > 0 ? contentView.frame.width : UIScreen.main.bounds.width
|
|
||||||
tagListView.frame.size.width = w - 170
|
|
||||||
}
|
}
|
||||||
tagListView.removeAllTags()
|
else {
|
||||||
tagListView.addTags(groupNames)
|
editBtn.isHidden = true
|
||||||
tagListView.tagViews.forEach {
|
followBtn.isHidden = model.is_follow ? true : false
|
||||||
$0.layer.cornerRadius = 2
|
|
||||||
}
|
}
|
||||||
tagListView.invalidateIntrinsicContentSize()
|
|
||||||
|
|
||||||
guard let pointModel = model.points.last else { return }
|
guard let pointModel = model.points.last else { return }
|
||||||
dateLab.text = getDateInterval2String(date: "\(pointModel.expected_timestamp/1000)", dateFormat: "MM.dd")
|
dateLab.text = getDateInterval2String(date: "\(pointModel.expected_timestamp/1000)", dateFormat: "MM.dd")
|
||||||
|
|
@ -352,47 +310,38 @@ class ScheduleListPopCell: UITableViewCell {
|
||||||
cornerView.addSubview(nameLab)
|
cornerView.addSubview(nameLab)
|
||||||
cornerView.addSubview(monthLab)
|
cornerView.addSubview(monthLab)
|
||||||
cornerView.addSubview(dateLab)
|
cornerView.addSubview(dateLab)
|
||||||
cornerView.addSubview(tagListView)
|
|
||||||
cornerView.addSubview(editBtn)
|
cornerView.addSubview(editBtn)
|
||||||
cornerView.addSubview(followBtn)
|
cornerView.addSubview(followBtn)
|
||||||
|
|
||||||
bgView.layoutChain
|
bgView.layoutChain
|
||||||
.top(15).bottom(15)
|
.edgesVertical(15)
|
||||||
.edgesHorzontal(15)
|
.edgesHorzontal(15)
|
||||||
|
.height(80)
|
||||||
|
|
||||||
cornerView.layoutChain.edges()
|
cornerView.layoutChain.edges()
|
||||||
|
|
||||||
monthLab.layoutChain
|
monthLab.layoutChain
|
||||||
.top(15)
|
.bottomToCenterYOfView(cornerView)
|
||||||
.left(11)
|
.left(11)
|
||||||
.width(35)
|
|
||||||
|
|
||||||
dateLab.layoutChain
|
dateLab.layoutChain
|
||||||
.topToBottomOfView(monthLab, offset: 4)
|
.topToBottomOfView(monthLab)
|
||||||
.leftToView(monthLab)
|
.leftToView(monthLab)
|
||||||
.width(35)
|
|
||||||
|
|
||||||
editBtn.layoutChain
|
|
||||||
.top(15)
|
|
||||||
.right(10)
|
|
||||||
.width(70)
|
|
||||||
.height(28)
|
|
||||||
|
|
||||||
nameLab.layoutChain
|
nameLab.layoutChain
|
||||||
.top(15)
|
.centerY()
|
||||||
.leftToRightOfView(monthLab, offset: 15)
|
.leftToRightOfView(monthLab, offset: 15)
|
||||||
|
|
||||||
tagListView.layoutChain
|
editBtn.layoutChain
|
||||||
.leftToRightOfView(dateLab, offset: 15)
|
.right(10)
|
||||||
.bottom(15)
|
.centerY()
|
||||||
.rightToLeftOfView(editBtn, offset: -10)
|
.width(56)
|
||||||
|
.height(28)
|
||||||
nameLab.layoutChain.bottomToTopOfView(tagListView, offset:-5)
|
|
||||||
|
|
||||||
followBtn.layoutChain
|
followBtn.layoutChain
|
||||||
.topToView(editBtn)
|
.topToView(editBtn)
|
||||||
.rightToView(editBtn)
|
.rightToView(editBtn)
|
||||||
.width(70)
|
.width(56)
|
||||||
.height(28)
|
.height(28)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -471,7 +420,6 @@ class ScheduleListPopCell: UITableViewCell {
|
||||||
let btn = UIButton()
|
let btn = UIButton()
|
||||||
btn.isHidden = true
|
btn.isHidden = true
|
||||||
btn.setTitle("关注", for: .normal)
|
btn.setTitle("关注", for: .normal)
|
||||||
btn.setTitle("取消关注", for: .selected)
|
|
||||||
btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
|
btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
|
||||||
btn.backgroundColor = .white
|
btn.backgroundColor = .white
|
||||||
|
|
@ -481,15 +429,4 @@ class ScheduleListPopCell: UITableViewCell {
|
||||||
return btn
|
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
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,13 +48,6 @@ class ScheduleViewModel: ViewModelType {
|
||||||
sectionedItems.onNext(list.mapSection())
|
sectionedItems.onNext(list.mapSection())
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy var viewedCellAction: Action<ViewedModel, Void> = { this in
|
|
||||||
return Action { model in
|
|
||||||
AppRouter.push(Route.scheduleViewed)
|
|
||||||
return .empty()
|
|
||||||
}
|
|
||||||
}(self)
|
|
||||||
|
|
||||||
lazy var cellAction: Action<ScheduleModel, Void> = { this in
|
lazy var cellAction: Action<ScheduleModel, Void> = { this in
|
||||||
return Action { model in
|
return Action { model in
|
||||||
AppRouter.push(Route.scheduleDetail, userInfo: ["scheduleJson": model.toJSON()])
|
AppRouter.push(Route.scheduleDetail, userInfo: ["scheduleJson": model.toJSON()])
|
||||||
|
|
@ -62,18 +55,6 @@ class ScheduleViewModel: ViewModelType {
|
||||||
}
|
}
|
||||||
}(self)
|
}(self)
|
||||||
|
|
||||||
/// 切换条件
|
|
||||||
/// - Parameters:
|
|
||||||
/// - follow: 只查看关注的行程
|
|
||||||
/// - own: 只查看自己创建的行程
|
|
||||||
/// - history: true查看历史行程,默认查看今天之后的行程
|
|
||||||
/// - group_key: 过滤圈子查询
|
|
||||||
func changeCondition(follow: Bool, own: Bool) {
|
|
||||||
listService.targetTransform = { page in
|
|
||||||
ItineraryAPI.query(follow: follow, own: own, history: false, group_key: "", page: page).multiTarget
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - init
|
// MARK: - init
|
||||||
init() {
|
init() {
|
||||||
listService = ItineraryService.query()
|
listService = ItineraryService.query()
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
//
|
|
||||||
// ScheduleViewedVC.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import RxDataSources
|
|
||||||
import MJRefresh
|
|
||||||
|
|
||||||
class ScheduleViewedVC: BaseViewController {
|
|
||||||
|
|
||||||
fileprivate var rootView: ScheduleViewedView!
|
|
||||||
|
|
||||||
override func loadView() {
|
|
||||||
rootView = ScheduleViewedView(frame: UIScreen.main.bounds)
|
|
||||||
view = rootView
|
|
||||||
}
|
|
||||||
|
|
||||||
private var viewModel = ScheduleViewedViewModel()
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
// Do any additional setup after loading the view.
|
|
||||||
bindViewModel()
|
|
||||||
requestFollowList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
|
||||||
super.viewWillAppear(animated)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func requestFollowList() {
|
|
||||||
DLToast.showLoading()
|
|
||||||
UserService.followList().subscribe(onNext: { response in
|
|
||||||
DLToast.dismiss()
|
|
||||||
self.viewModel.loadViewedData(response.list)
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func bindViewModel() {
|
|
||||||
viewModel.output.sectionedItems
|
|
||||||
.bind(to: rootView.tableView.rx.items(dataSource: tableViewDataSource))
|
|
||||||
.disposed(by: disposeBag)
|
|
||||||
|
|
||||||
rootView.tableView.rx.modelSelected(ViewedModel.self)
|
|
||||||
.subscribe(viewModel.cellAction.inputs)
|
|
||||||
.disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - dataSource
|
|
||||||
lazy private var tableViewDataSource: RxTableViewSectionedReloadDataSource<ViewedListSectionModel> = {
|
|
||||||
return RxTableViewSectionedReloadDataSource<ViewedListSectionModel>(
|
|
||||||
configureCell: { (_, tableView, indexPath, model) in
|
|
||||||
let cell: ScheduleViewedListCell = tableView.dequeueReusableCell(for: indexPath)
|
|
||||||
cell.configure(model)
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
//
|
|
||||||
// ScheduleViewedVM.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import RxDataSources
|
|
||||||
import ObjectMapper
|
|
||||||
|
|
||||||
struct ScheduleViewedViewModel {
|
|
||||||
|
|
||||||
struct Output {
|
|
||||||
var sectionedItems: Observable<[ViewedListSectionModel]>
|
|
||||||
}
|
|
||||||
|
|
||||||
let output: Output
|
|
||||||
|
|
||||||
private let sectionedItems = PublishSubject<[ViewedListSectionModel]>()
|
|
||||||
|
|
||||||
lazy var cellAction: Action<ViewedModel, Void> = { this in
|
|
||||||
return Action { model in
|
|
||||||
|
|
||||||
return .empty()
|
|
||||||
}
|
|
||||||
}(self)
|
|
||||||
|
|
||||||
func loadViewedData(_ list: [ViewedModel]) {
|
|
||||||
sectionedItems.onNext(list.mapSection())
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - init
|
|
||||||
init() {
|
|
||||||
output = Output(
|
|
||||||
sectionedItems: sectionedItems
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,258 +0,0 @@
|
||||||
//
|
|
||||||
// ScheduleViewedView.swift
|
|
||||||
// QuickLocation
|
|
||||||
//
|
|
||||||
// Created by 八条 on 2026/6/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import RxSwift
|
|
||||||
import RxCocoa
|
|
||||||
import TagListView
|
|
||||||
|
|
||||||
class ScheduleViewedView: UIView {
|
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
|
||||||
|
|
||||||
private func setupRx() {
|
|
||||||
backBtn.rx.tap.subscribe(onNext: { _ in
|
|
||||||
AppRouter.shared.popOrDismiss()
|
|
||||||
}).disposed(by: disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupUI() {
|
|
||||||
addSubview(navBgView)
|
|
||||||
addSubview(navBarView)
|
|
||||||
navBarView.addSubview(navTitleLabel)
|
|
||||||
navBarView.addSubview(backBtn)
|
|
||||||
addSubview(tableView)
|
|
||||||
|
|
||||||
navBgView.layoutChain
|
|
||||||
.edges(excludingEdge: .bottom)
|
|
||||||
.heightToWidth(160/375)
|
|
||||||
|
|
||||||
navBarView.layoutChain
|
|
||||||
.edges(excludingEdge: .bottom)
|
|
||||||
.height(kNaviHeight)
|
|
||||||
|
|
||||||
navTitleLabel.layoutChain
|
|
||||||
.top(kStatusBarHeight + 12)
|
|
||||||
.centerY(backBtn)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
backBtn.layoutChain
|
|
||||||
.centerY(navTitleLabel)
|
|
||||||
.left(15)
|
|
||||||
.width(24)
|
|
||||||
.height(24)
|
|
||||||
|
|
||||||
tableView.layoutChain
|
|
||||||
.topToBottomOfView(navBarView, offset: 15)
|
|
||||||
.edges(excludingEdge: .top)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var navBgView: UIImageView = {
|
|
||||||
let iv = UIImageView()
|
|
||||||
iv.image = UIImage(named: "Common/navBar_bg_2")
|
|
||||||
iv.contentMode = .scaleAspectFill
|
|
||||||
return iv
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var navBarView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.backgroundColor = .clear
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var navTitleLabel: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = "看过我的"
|
|
||||||
label.font = .systemFont(ofSize: 18, weight: .medium)
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
label.textAlignment = .center
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var backBtn: UIButton = {
|
|
||||||
let btn = UIButton(type: .custom)
|
|
||||||
btn.setImage(UIImage(named: "Common/back"), for: .normal)
|
|
||||||
btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100)
|
|
||||||
return btn
|
|
||||||
}()
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: .zero)
|
|
||||||
backgroundColor = .white
|
|
||||||
setupUI()
|
|
||||||
setupRx()
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var tableView: UITableView = {
|
|
||||||
let tableView = UITableView(frame: .zero, style: .plain)
|
|
||||||
tableView.backgroundColor = .clear
|
|
||||||
tableView.separatorStyle = .none
|
|
||||||
tableView.estimatedRowHeight = 86
|
|
||||||
tableView.rowHeight = UITableView.automaticDimension
|
|
||||||
tableView.showsVerticalScrollIndicator = false
|
|
||||||
tableView.register(ScheduleViewedListCell.self)
|
|
||||||
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: kSafeBottomMargin + 20, right: 0)
|
|
||||||
return tableView
|
|
||||||
}()
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - ScheduleViewedListCell
|
|
||||||
class ScheduleViewedListCell: UITableViewCell {
|
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
|
||||||
|
|
||||||
func configure(_ model: ViewedModel) {
|
|
||||||
iconView.image = model.userIcon
|
|
||||||
nameLab.text = model.nick_name
|
|
||||||
viewedCountLab.text = "看过我\(model.count)次"
|
|
||||||
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 - 185
|
|
||||||
}
|
|
||||||
tagListView.removeAllTags()
|
|
||||||
tagListView.addTags(groupNames)
|
|
||||||
tagListView.tagViews.forEach {
|
|
||||||
$0.layer.cornerRadius = 2
|
|
||||||
}
|
|
||||||
tagListView.invalidateIntrinsicContentSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
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(iconView)
|
|
||||||
cornerView.addSubview(nameLab)
|
|
||||||
cornerView.addSubview(viewedCountLab)
|
|
||||||
cornerView.addSubview(tagTitleLab)
|
|
||||||
cornerView.addSubview(tagListView)
|
|
||||||
|
|
||||||
bgView.layoutChain
|
|
||||||
.top(10).bottom()
|
|
||||||
.edgesHorzontal(15)
|
|
||||||
|
|
||||||
cornerView.layoutChain.edges()
|
|
||||||
|
|
||||||
iconView.layoutChain
|
|
||||||
.left(15)
|
|
||||||
.top(18)
|
|
||||||
.height(50)
|
|
||||||
.widthToHeight(1)
|
|
||||||
|
|
||||||
nameLab.layoutChain
|
|
||||||
.topToView(iconView)
|
|
||||||
.leftToRightOfView(iconView, offset: 10)
|
|
||||||
|
|
||||||
viewedCountLab.layoutChain
|
|
||||||
.right(15)
|
|
||||||
.centerY(nameLab)
|
|
||||||
|
|
||||||
tagTitleLab.layoutChain
|
|
||||||
.topToBottomOfView(nameLab, offset: 9)
|
|
||||||
.leftToView(nameLab)
|
|
||||||
.width(60)
|
|
||||||
|
|
||||||
tagListView.layoutChain
|
|
||||||
.topToBottomOfView(nameLab, offset: 5)
|
|
||||||
.leftToRightOfView(tagTitleLab, offset: 5)
|
|
||||||
.bottom(20)
|
|
||||||
.right(15)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 iconView: UIImageView = {
|
|
||||||
let view = UIImageView()
|
|
||||||
view.backgroundColor = .clear
|
|
||||||
view.contentMode = .scaleAspectFill
|
|
||||||
view.cornerRadius = 25
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var nameLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
||||||
label.font = .systemFont(ofSize: 14, weight: .medium)
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var viewedCountLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.textColor = UIColor(hexStr: "#999999")
|
|
||||||
label.font = .systemFont(ofSize: 12, weight: .medium)
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var tagTitleLab: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.text = "共同的圈子:"
|
|
||||||
label.textColor = UIColor(hexStr: "#999999")
|
|
||||||
label.font = .systemFont(ofSize: 10, weight: .medium)
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
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
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
@ -39,9 +39,6 @@ struct ViewedModel: Mappable, Equatable {
|
||||||
/// 查看次数
|
/// 查看次数
|
||||||
var count: Int = 0
|
var count: Int = 0
|
||||||
|
|
||||||
/// 共同圈子
|
|
||||||
var groups: [GroupCommonModel] = []
|
|
||||||
|
|
||||||
init?(map: Map) {
|
init?(map: Map) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -51,7 +48,6 @@ struct ViewedModel: Mappable, Equatable {
|
||||||
nick_name <- map["nick_name"]
|
nick_name <- map["nick_name"]
|
||||||
head_pic <- map["head_pic"]
|
head_pic <- map["head_pic"]
|
||||||
count <- map["count"]
|
count <- map["count"]
|
||||||
groups <- map["groups"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@ struct ItineraryService {
|
||||||
/// 查询行程关注人
|
/// 查询行程关注人
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - id: 行程ID
|
/// - id: 行程ID
|
||||||
static func queryFollowList(id: String) -> Observable<ViewedListResponse> {
|
static func queryFollowList(id: String) -> Observable<ResponseModel> {
|
||||||
let api = ItineraryAPI.queryFollowList(id: id).multiTarget
|
let api = ItineraryAPI.queryFollowList(id: id).multiTarget
|
||||||
return APIProvider.request(token: api)
|
return APIProvider.request(token: api)
|
||||||
.map(ViewedListResponse.self)
|
.map(ResponseModel.self)
|
||||||
.asObservable()
|
.asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,15 +62,4 @@ struct ItineraryService {
|
||||||
.map(ResponseModel.self)
|
.map(ResponseModel.self)
|
||||||
.asObservable()
|
.asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 关注行程
|
|
||||||
/// - Parameters:
|
|
||||||
/// - id: 行程ID
|
|
||||||
/// - op: 1关注 2取消关注
|
|
||||||
static func follow(id: String, op: Int) -> Observable<ResponseModel> {
|
|
||||||
let api = ItineraryAPI.follow(id: id, op: op).multiTarget
|
|
||||||
return APIProvider.request(token: api)
|
|
||||||
.map(ResponseModel.self)
|
|
||||||
.asObservable()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,25 +41,4 @@ struct SystemService {
|
||||||
.map(ResponseModel.self)
|
.map(ResponseModel.self)
|
||||||
.asObservable()
|
.asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 查找
|
|
||||||
/// - Parameters:
|
|
||||||
/// - op_type: plate_num 车牌 phone 手机号
|
|
||||||
/// - number: 对应号码
|
|
||||||
static func search(op_type: String, number: String) -> Observable<ResponseModel> {
|
|
||||||
let api = SystemAPI.search(op_type: op_type, number: number).multiTarget
|
|
||||||
return APIProvider.request(token: api, handle: false)
|
|
||||||
.map(ResponseModel.self)
|
|
||||||
.asObservable()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 手机归属地
|
|
||||||
/// - Parameters:
|
|
||||||
/// - phone 手机号
|
|
||||||
static func phoneArea(phone: String) -> Observable<ResponseModel> {
|
|
||||||
let api = SystemAPI.phoneArea(phone: phone).multiTarget
|
|
||||||
return APIProvider.request(token: api)
|
|
||||||
.map(ResponseModel.self)
|
|
||||||
.asObservable()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -140,15 +140,4 @@ struct UserService {
|
||||||
.map(ViewedListResponse.self)
|
.map(ViewedListResponse.self)
|
||||||
.asObservable()
|
.asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 设置气泡
|
|
||||||
/// - Parameters:
|
|
||||||
/// - enable:
|
|
||||||
/// - keep_time:
|
|
||||||
static func setBubble(enable: Bool, keep_time: Int) -> Observable<ResponseModel> {
|
|
||||||
let api = UserAPI.bubble(enable: enable, keep_time: keep_time).multiTarget
|
|
||||||
return APIProvider.request(token: api)
|
|
||||||
.map(ResponseModel.self)
|
|
||||||
.asObservable()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||