diff --git a/QuickLocation.xcodeproj/project.pbxproj b/QuickLocation.xcodeproj/project.pbxproj index 0aca42c..0c2bc75 100644 --- a/QuickLocation.xcodeproj/project.pbxproj +++ b/QuickLocation.xcodeproj/project.pbxproj @@ -190,6 +190,10 @@ 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 */; }; 30BAB84F2FCD2FED00C33B5C /* InviteJoinVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB84E2FCD2FED00C33B5C /* InviteJoinVC.swift */; }; 30BAB8512FCD331C00C33B5C /* GroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8502FCD331C00C33B5C /* GroupAPI.swift */; }; @@ -241,6 +245,7 @@ 30DC185C2FD11E7A0041DCD1 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18572FD11E7A0041DCD1 /* WebViewController.swift */; }; 30DC185E2FD1211D0041DCD1 /* VipRightsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC185D2FD1211D0041DCD1 /* VipRightsVC.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 */; }; 30EFF29B2FD668C900EB35D4 /* VoiceRecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF29A2FD668C900EB35D4 /* VoiceRecordView.swift */; }; 30EFF3A42FD7C5A300EB35D4 /* GroupSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3A32FD7C5A300EB35D4 /* GroupSettingView.swift */; }; @@ -467,6 +472,10 @@ 30A87A632FEE75520095E7C6 /* CreateBubbleTipsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubbleTipsView.swift; sourceTree = ""; }; 30A87A652FEE843E0095E7C6 /* CreateBubbleDoneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubbleDoneView.swift; sourceTree = ""; }; 30A87A672FEE86560095E7C6 /* CreateBubblePopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubblePopView.swift; sourceTree = ""; }; + 30A87A6A2FEF5B950095E7C6 /* SearchLocationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationView.swift; sourceTree = ""; }; + 30A87A6C2FEF5BA10095E7C6 /* SearchLocationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationVC.swift; sourceTree = ""; }; + 30A87A6E2FEF7BE40095E7C6 /* SearchLocationResultVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationResultVC.swift; sourceTree = ""; }; + 30A87A702FEF7BED0095E7C6 /* SearchLocationResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationResultView.swift; sourceTree = ""; }; 30BAB84C2FCD2FDE00C33B5C /* InviteJoinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteJoinView.swift; sourceTree = ""; }; 30BAB84E2FCD2FED00C33B5C /* InviteJoinVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteJoinVC.swift; sourceTree = ""; }; 30BAB8502FCD331C00C33B5C /* GroupAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupAPI.swift; sourceTree = ""; }; @@ -519,6 +528,7 @@ 30DC18572FD11E7A0041DCD1 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; 30DC185D2FD1211D0041DCD1 /* VipRightsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipRightsVC.swift; sourceTree = ""; }; 30DC185F2FD12A020041DCD1 /* VipWaivePopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipWaivePopView.swift; sourceTree = ""; }; + 30EBF71F2FEFD6F2009A8A87 /* GroupChooseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChooseView.swift; sourceTree = ""; }; 30EFF2982FD65FB000EB35D4 /* VoicePlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoicePlayerManager.swift; sourceTree = ""; }; 30EFF29A2FD668C900EB35D4 /* VoiceRecordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceRecordView.swift; sourceTree = ""; }; 30EFF3A02FD7A47900EB35D4 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.storyboard"; sourceTree = ""; }; @@ -958,6 +968,7 @@ 30A7A9102FCAEE3D00105780 /* GroupListPopView.swift */, 30D87CDE2FDFF1A100E958FD /* QuickMessageView.swift */, 30D87CDC2FDFF07500E958FD /* InteractionView.swift */, + 30A87A692FEF59E60095E7C6 /* SearchLocation */, 30A87A5C2FEE711C0095E7C6 /* Bubble */, 30CCDE4F2FE2782700F5214A /* SignIn */, 30CCDE562FE39F6B00F5214A /* SOS */, @@ -1287,6 +1298,18 @@ path = Bubble; sourceTree = ""; }; + 30A87A692FEF59E60095E7C6 /* SearchLocation */ = { + isa = PBXGroup; + children = ( + 30A87A6C2FEF5BA10095E7C6 /* SearchLocationVC.swift */, + 30A87A6A2FEF5B950095E7C6 /* SearchLocationView.swift */, + 30A87A6E2FEF7BE40095E7C6 /* SearchLocationResultVC.swift */, + 30A87A702FEF7BED0095E7C6 /* SearchLocationResultView.swift */, + 30EBF71F2FEFD6F2009A8A87 /* GroupChooseView.swift */, + ); + path = SearchLocation; + sourceTree = ""; + }; 30BAB84B2FCD2FA400C33B5C /* InviteJoin */ = { isa = PBXGroup; children = ( @@ -1699,6 +1722,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 30A87A712FEF7BED0095E7C6 /* SearchLocationResultView.swift in Sources */, 305A76882FCA8C7000227D26 /* MoyaProvider+Rx.swift in Sources */, 305A76892FCA8C7000227D26 /* Observable+Response.swift in Sources */, 305A768A2FCA8C7000227D26 /* Single+Response.swift in Sources */, @@ -1796,6 +1820,7 @@ 30EFF3C02FD958AE00EB35D4 /* AccountVC.swift in Sources */, 30C4C0202FDC0EC5009215C1 /* GroupInfoView.swift in Sources */, 305A76BF2FCA8C7000227D26 /* ListService.swift in Sources */, + 30A87A6D2FEF5BA10095E7C6 /* SearchLocationVC.swift in Sources */, 305A76C02FCA8C7000227D26 /* BaseNavigationController.swift in Sources */, 305A76C12FCA8C7000227D26 /* BaseViewController.swift in Sources */, 3062E8C02FCED7BB00CEF511 /* GroupIconListView.swift in Sources */, @@ -1839,6 +1864,7 @@ 305A76D82FCA8C7000227D26 /* Action.swift in Sources */, 30EFF3B02FD8122E00EB35D4 /* GroupTagListView.swift in Sources */, 305A76D92FCA8C7000227D26 /* Action+Internal.swift in Sources */, + 30EBF7202FEFD6F2009A8A87 /* GroupChooseView.swift in Sources */, 305A76DA2FCA8C7000227D26 /* Button+Action.swift in Sources */, 305A76DB2FCA8C7000227D26 /* Control+Action.swift in Sources */, 305A76DC2FCA8C7000227D26 /* InputSubject.swift in Sources */, @@ -1849,6 +1875,7 @@ 305A76DF2FCA8C7000227D26 /* Single+ObjectMapper.swift in Sources */, 30DC18602FD12A020041DCD1 /* VipWaivePopView.swift in Sources */, 30D74AAB2FE8C7700050EB2C /* GPSSignalHelper.swift in Sources */, + 30A87A6B2FEF5B950095E7C6 /* SearchLocationView.swift in Sources */, 305A76E02FCA8C7000227D26 /* GroupView.swift in Sources */, 30EFF3BB2FD90D7600EB35D4 /* ConfirmPopVC.swift in Sources */, 30BAB8512FCD331C00C33B5C /* GroupAPI.swift in Sources */, @@ -1880,6 +1907,7 @@ 305A76EA2FCA8C7000227D26 /* OneTapLoginView.swift in Sources */, 305A76EB2FCA8C7000227D26 /* CircleMember.swift in Sources */, 30CCDE5A2FE39F9D00F5214A /* SOSViewController.swift in Sources */, + 30A87A6F2FEF7BE40095E7C6 /* SearchLocationResultVC.swift in Sources */, 30D74AB42FEA25B90050EB2C /* ViewedModel.swift in Sources */, 305A76EC2FCA8C7000227D26 /* MemberAnnotation.swift in Sources */, 305A76ED2FCA8C7000227D26 /* MemberAnnotationView.swift in Sources */, diff --git a/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate b/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate index 19a2575..1ff7333 100644 Binary files a/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate and b/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/QuickLocation/API/SystemAPI.swift b/QuickLocation/API/SystemAPI.swift index 98d5fd1..6918384 100644 --- a/QuickLocation/API/SystemAPI.swift +++ b/QuickLocation/API/SystemAPI.swift @@ -23,6 +23,18 @@ enum SystemAPI { /// SOS 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 { @@ -37,14 +49,18 @@ extension SystemAPI: MultiTargetProtocol { return "api/weixin/service" case .sos: return "mapi/sos/operate" + case .search: + return "mapi/car/search" + case .phoneArea: + return "mapi/phonearea" } } var method: Moya.Method { switch self { - case .userConfig, .wechatService: + case .userConfig, .wechatService, .phoneArea: return .get - case .sendCode, .sos: + default: return .post } } @@ -67,6 +83,16 @@ extension SystemAPI: MultiTargetProtocol { params["enable"] = enable 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()) } } } diff --git a/QuickLocation/API/UserAPI.swift b/QuickLocation/API/UserAPI.swift index 67cbd23..57ab726 100644 --- a/QuickLocation/API/UserAPI.swift +++ b/QuickLocation/API/UserAPI.swift @@ -195,7 +195,9 @@ extension UserAPI: MultiTargetProtocol { case let .bubble(enable, keep_time): var params = Parameters() params["enable"] = enable - params["keep_time"] = keep_time + if keep_time != -1 { + params["keep_time"] = keep_time + } return .requestParameters(parameters: params, encoding: JSONEncoding()) } } diff --git a/QuickLocation/Assets.xcassets/Bubble/text.imageset/Contents.json b/QuickLocation/Assets.xcassets/Bubble/text.imageset/Contents.json new file mode 100644 index 0000000..8350eea --- /dev/null +++ b/QuickLocation/Assets.xcassets/Bubble/text.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "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 + } +} diff --git a/QuickLocation/Assets.xcassets/Bubble/text.imageset/text@2x.png b/QuickLocation/Assets.xcassets/Bubble/text.imageset/text@2x.png new file mode 100644 index 0000000..d8163c6 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Bubble/text.imageset/text@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Bubble/text.imageset/text@3x.png b/QuickLocation/Assets.xcassets/Bubble/text.imageset/text@3x.png new file mode 100644 index 0000000..c186a82 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Bubble/text.imageset/text@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Bubble/vip_pop.imageset/Contents.json b/QuickLocation/Assets.xcassets/Bubble/vip_pop.imageset/Contents.json new file mode 100644 index 0000000..4379e22 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Bubble/vip_pop.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "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 + } +} diff --git a/QuickLocation/Assets.xcassets/Bubble/vip_pop.imageset/vip_pop@2x.png b/QuickLocation/Assets.xcassets/Bubble/vip_pop.imageset/vip_pop@2x.png new file mode 100644 index 0000000..470a49b Binary files /dev/null and b/QuickLocation/Assets.xcassets/Bubble/vip_pop.imageset/vip_pop@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Bubble/vip_pop.imageset/vip_pop@3x.png b/QuickLocation/Assets.xcassets/Bubble/vip_pop.imageset/vip_pop@3x.png new file mode 100644 index 0000000..99af46a Binary files /dev/null and b/QuickLocation/Assets.xcassets/Bubble/vip_pop.imageset/vip_pop@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Home/schedule.imageset/Contents.json b/QuickLocation/Assets.xcassets/Home/schedule.imageset/Contents.json new file mode 100644 index 0000000..373ced7 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Home/schedule.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "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 + } +} diff --git a/QuickLocation/Assets.xcassets/Home/schedule.imageset/schedule@2x.png b/QuickLocation/Assets.xcassets/Home/schedule.imageset/schedule@2x.png new file mode 100644 index 0000000..baa3f30 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Home/schedule.imageset/schedule@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Home/schedule.imageset/schedule@3x.png b/QuickLocation/Assets.xcassets/Home/schedule.imageset/schedule@3x.png new file mode 100644 index 0000000..cbd4365 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Home/schedule.imageset/schedule@3x.png differ diff --git a/QuickLocation/Assets.xcassets/SearchLocation/Contents.json b/QuickLocation/Assets.xcassets/SearchLocation/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/QuickLocation/Assets.xcassets/SearchLocation/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/QuickLocation/Assets.xcassets/SearchLocation/bg_1.imageset/Contents.json b/QuickLocation/Assets.xcassets/SearchLocation/bg_1.imageset/Contents.json new file mode 100644 index 0000000..aeb0a70 --- /dev/null +++ b/QuickLocation/Assets.xcassets/SearchLocation/bg_1.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "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 + } +} diff --git a/QuickLocation/Assets.xcassets/SearchLocation/bg_1.imageset/Group_1554@2x.png b/QuickLocation/Assets.xcassets/SearchLocation/bg_1.imageset/Group_1554@2x.png new file mode 100644 index 0000000..3eb6078 Binary files /dev/null and b/QuickLocation/Assets.xcassets/SearchLocation/bg_1.imageset/Group_1554@2x.png differ diff --git a/QuickLocation/Assets.xcassets/SearchLocation/bg_1.imageset/Group_1554@3x.png b/QuickLocation/Assets.xcassets/SearchLocation/bg_1.imageset/Group_1554@3x.png new file mode 100644 index 0000000..e7b19fe Binary files /dev/null and b/QuickLocation/Assets.xcassets/SearchLocation/bg_1.imageset/Group_1554@3x.png differ diff --git a/QuickLocation/Assets.xcassets/SearchLocation/contacts.imageset/Contents.json b/QuickLocation/Assets.xcassets/SearchLocation/contacts.imageset/Contents.json new file mode 100644 index 0000000..738e945 --- /dev/null +++ b/QuickLocation/Assets.xcassets/SearchLocation/contacts.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "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 + } +} diff --git a/QuickLocation/Assets.xcassets/SearchLocation/contacts.imageset/Group_1677@2x.png b/QuickLocation/Assets.xcassets/SearchLocation/contacts.imageset/Group_1677@2x.png new file mode 100644 index 0000000..576f8e3 Binary files /dev/null and b/QuickLocation/Assets.xcassets/SearchLocation/contacts.imageset/Group_1677@2x.png differ diff --git a/QuickLocation/Assets.xcassets/SearchLocation/contacts.imageset/Group_1677@3x.png b/QuickLocation/Assets.xcassets/SearchLocation/contacts.imageset/Group_1677@3x.png new file mode 100644 index 0000000..5e39ba2 Binary files /dev/null and b/QuickLocation/Assets.xcassets/SearchLocation/contacts.imageset/Group_1677@3x.png differ diff --git a/QuickLocation/Assets.xcassets/SearchLocation/done.imageset/Contents.json b/QuickLocation/Assets.xcassets/SearchLocation/done.imageset/Contents.json new file mode 100644 index 0000000..4388354 --- /dev/null +++ b/QuickLocation/Assets.xcassets/SearchLocation/done.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "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 + } +} diff --git a/QuickLocation/Assets.xcassets/SearchLocation/done.imageset/Group_686@2x.png b/QuickLocation/Assets.xcassets/SearchLocation/done.imageset/Group_686@2x.png new file mode 100644 index 0000000..d7befaf Binary files /dev/null and b/QuickLocation/Assets.xcassets/SearchLocation/done.imageset/Group_686@2x.png differ diff --git a/QuickLocation/Assets.xcassets/SearchLocation/done.imageset/Group_686@3x.png b/QuickLocation/Assets.xcassets/SearchLocation/done.imageset/Group_686@3x.png new file mode 100644 index 0000000..d540260 Binary files /dev/null and b/QuickLocation/Assets.xcassets/SearchLocation/done.imageset/Group_686@3x.png differ diff --git a/QuickLocation/Assets.xcassets/SearchLocation/done_off.imageset/Contents.json b/QuickLocation/Assets.xcassets/SearchLocation/done_off.imageset/Contents.json new file mode 100644 index 0000000..534ab51 --- /dev/null +++ b/QuickLocation/Assets.xcassets/SearchLocation/done_off.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "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 + } +} diff --git a/QuickLocation/Assets.xcassets/SearchLocation/done_off.imageset/Group_687@2x.png b/QuickLocation/Assets.xcassets/SearchLocation/done_off.imageset/Group_687@2x.png new file mode 100644 index 0000000..fa8d49b Binary files /dev/null and b/QuickLocation/Assets.xcassets/SearchLocation/done_off.imageset/Group_687@2x.png differ diff --git a/QuickLocation/Assets.xcassets/SearchLocation/done_off.imageset/Group_687@3x.png b/QuickLocation/Assets.xcassets/SearchLocation/done_off.imageset/Group_687@3x.png new file mode 100644 index 0000000..3a25937 Binary files /dev/null and b/QuickLocation/Assets.xcassets/SearchLocation/done_off.imageset/Group_687@3x.png differ diff --git a/QuickLocation/Assets.xcassets/SearchLocation/mask.imageset/Contents.json b/QuickLocation/Assets.xcassets/SearchLocation/mask.imageset/Contents.json new file mode 100644 index 0000000..e8ade83 --- /dev/null +++ b/QuickLocation/Assets.xcassets/SearchLocation/mask.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "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 + } +} diff --git a/QuickLocation/Assets.xcassets/SearchLocation/mask.imageset/mask@2x.png b/QuickLocation/Assets.xcassets/SearchLocation/mask.imageset/mask@2x.png new file mode 100644 index 0000000..112c593 Binary files /dev/null and b/QuickLocation/Assets.xcassets/SearchLocation/mask.imageset/mask@2x.png differ diff --git a/QuickLocation/Assets.xcassets/SearchLocation/mask.imageset/mask@3x.png b/QuickLocation/Assets.xcassets/SearchLocation/mask.imageset/mask@3x.png new file mode 100644 index 0000000..8440c20 Binary files /dev/null and b/QuickLocation/Assets.xcassets/SearchLocation/mask.imageset/mask@3x.png differ diff --git a/QuickLocation/Assets.xcassets/SearchLocation/result_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/SearchLocation/result_bg.imageset/Contents.json new file mode 100644 index 0000000..0ebc847 --- /dev/null +++ b/QuickLocation/Assets.xcassets/SearchLocation/result_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "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 + } +} diff --git a/QuickLocation/Assets.xcassets/SearchLocation/result_bg.imageset/result_bg@2x.png b/QuickLocation/Assets.xcassets/SearchLocation/result_bg.imageset/result_bg@2x.png new file mode 100644 index 0000000..f41215a Binary files /dev/null and b/QuickLocation/Assets.xcassets/SearchLocation/result_bg.imageset/result_bg@2x.png differ diff --git a/QuickLocation/Assets.xcassets/SearchLocation/result_bg.imageset/result_bg@3x.png b/QuickLocation/Assets.xcassets/SearchLocation/result_bg.imageset/result_bg@3x.png new file mode 100644 index 0000000..75c4fa7 Binary files /dev/null and b/QuickLocation/Assets.xcassets/SearchLocation/result_bg.imageset/result_bg@3x.png differ diff --git a/QuickLocation/Common/Constant.swift b/QuickLocation/Common/Constant.swift index a5bd0aa..5eb6867 100644 --- a/QuickLocation/Common/Constant.swift +++ b/QuickLocation/Common/Constant.swift @@ -31,6 +31,8 @@ extension Notification.Name { static let RefreshGroupInfoNotification = Notification.Name("RefreshGroupInfoNotification") /// 刷新IM圈子列表数据 static let RefreshIMGroupListNotification = Notification.Name("RefreshIMGroupListNotification") + /// 查看成员位置 + static let ShowMemberLocationNotification = Notification.Name("ShowMemberLocationNotification") /// 支付宝/微信支付结果回调 static let RequestOrderPayStatusNotification = Notification.Name("RequestOrderPayStatusNotification") } diff --git a/QuickLocation/Core/Extension/String+Extension.swift b/QuickLocation/Core/Extension/String+Extension.swift index 21fef0d..283808a 100644 --- a/QuickLocation/Core/Extension/String+Extension.swift +++ b/QuickLocation/Core/Extension/String+Extension.swift @@ -512,3 +512,10 @@ extension String { return nil } } + +extension String { + var isPhoneNumber: Bool { + let reg = "^1[3-9]\\d{9}$" + return NSPredicate(format: "SELF MATCHES %@", reg).evaluate(with: self) + } +} diff --git a/QuickLocation/Main/BaseViewController/BaseViewController.swift b/QuickLocation/Main/BaseViewController/BaseViewController.swift index ce8d4d7..929ce95 100644 --- a/QuickLocation/Main/BaseViewController/BaseViewController.swift +++ b/QuickLocation/Main/BaseViewController/BaseViewController.swift @@ -59,9 +59,15 @@ class BaseViewController: UIViewController { (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) { super.viewWillDisappear(animated) - + let className = String(describing: self.classForCoder) print(className) } diff --git a/QuickLocation/Manager/App/RouterManager.swift b/QuickLocation/Manager/App/RouterManager.swift index 2998a67..6a804f5 100644 --- a/QuickLocation/Manager/App/RouterManager.swift +++ b/QuickLocation/Manager/App/RouterManager.swift @@ -65,6 +65,10 @@ enum Route: String { case scheduleViewed = "scheduleViewed" /// 创建气泡 case createBubble = "createBubble" + /// 查找位置 + case searchLocation = "searchLocation" + /// 查找位置结果 + case searchLocationResult = "searchLocationResult" } extension Route: RouterTarget { @@ -305,6 +309,20 @@ extension AppRouter: AppRouterProtocol { 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]) + } } } diff --git a/QuickLocation/Section/Home/Bubble/CreateBubbleDoneView.swift b/QuickLocation/Section/Home/Bubble/CreateBubbleDoneView.swift index ec682c5..d360451 100644 --- a/QuickLocation/Section/Home/Bubble/CreateBubbleDoneView.swift +++ b/QuickLocation/Section/Home/Bubble/CreateBubbleDoneView.swift @@ -13,6 +13,32 @@ 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() { } @@ -23,6 +49,7 @@ class CreateBubbleDoneView: UIView { addSubview(timeLab) addSubview(messageView) addSubview(iconView) + addSubview(scrollView) addSubview(popupBtn) titleLab.layoutChain @@ -38,7 +65,7 @@ class CreateBubbleDoneView: UIView { .centerY(timeLab) messageView.layoutChain - .topToBottomOfView(timeLab, offset: 41) + .topToBottomOfView(timeLab, offset: 25) .left(79) .right(30) @@ -53,6 +80,11 @@ class CreateBubbleDoneView: UIView { .height(50) .centerX() .bottom(kSafeBottomMargin + 20) + + scrollView.layoutChain + .topToBottomOfView(messageView, offset: 20) + .edgesHorzontal() + .bottomToTopOfView(popupBtn, offset: -20) } lazy var titleLab: UILabel = { @@ -114,35 +146,27 @@ class CreateBubbleDoneView: UIView { return label }() - 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) - } - - deinit { - countdownTimer?.invalidate() - } + 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) @@ -155,5 +179,9 @@ class CreateBubbleDoneView: UIView { required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + deinit { + countdownTimer?.invalidate() + } } diff --git a/QuickLocation/Section/Home/Bubble/CreateBubbleVC.swift b/QuickLocation/Section/Home/Bubble/CreateBubbleVC.swift index 6d8f7b4..e5796fd 100644 --- a/QuickLocation/Section/Home/Bubble/CreateBubbleVC.swift +++ b/QuickLocation/Section/Home/Bubble/CreateBubbleVC.swift @@ -17,11 +17,19 @@ class CreateBubbleVC: BaseViewController { 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() - rootView.createBubbleTipsView.doneBtn.rx.tap.subscribe(onNext: { [weak self] in + 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 @@ -31,12 +39,28 @@ class CreateBubbleVC: BaseViewController { 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() diff --git a/QuickLocation/Section/Home/GroupMemberView.swift b/QuickLocation/Section/Home/GroupMemberView.swift index 832db91..a846872 100644 --- a/QuickLocation/Section/Home/GroupMemberView.swift +++ b/QuickLocation/Section/Home/GroupMemberView.swift @@ -133,10 +133,9 @@ class GroupMemberView: UIView { tableView.backgroundColor = .clear tableView.separatorStyle = .none tableView.estimatedRowHeight = 76 - tableView.bounces = false tableView.showsVerticalScrollIndicator = false - tableView.isScrollEnabled = false tableView.register(GroupMemberCell.self) + tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0) return tableView }() diff --git a/QuickLocation/Section/Home/HomeView.swift b/QuickLocation/Section/Home/HomeView.swift index 44fc586..5d6598d 100644 --- a/QuickLocation/Section/Home/HomeView.swift +++ b/QuickLocation/Section/Home/HomeView.swift @@ -88,21 +88,20 @@ class HomeView: UIView { .disposed(by: disposeBag) - // tableView 到达顶部继续下拉时,改由 GroupMemberView 的 pan 手势接管 + // 嵌套滑动协调:contentOffset 只复位位置,scroll 状态由 pan handler + panVelocity 管理 groupMemberView.tableView.rx.contentOffset .subscribe(onNext: { [weak self] offset in guard let self = self else { return } - if self.isSubCanScroll { - if offset.y <= 0 { - self.isSubCanScroll = false - self.groupMemberView.tableView.setContentOffset(.zero, animated: false) - } - } else if offset.y != 0 { + if !self.isSubCanScroll && offset.y != 0 { self.groupMemberView.tableView.setContentOffset(.zero, animated: false) } }) .disposed(by: disposeBag) + // 查找位置 + searchLottieView.rx.tapGesture.subscribe { _ in + AppRouter.push(Route.searchLocation) + }.disposed(by: disposeBag) } private func setupUI() { @@ -315,13 +314,23 @@ class HomeView: UIView { // 只有 tableView 到达顶部继续下拉时,才切换为 view 拖拽 if isSubCanScroll { let tableViewOffset = self.groupMemberView.tableView.contentOffset.y - if tableViewOffset > 0, newTop >= groupMemberUpLimit { - // 还未滑到顶部,让 tableView 处理 + if tableViewOffset > 0 { + // 内容正在滑动,不移动 GroupMemberView + return + } + // 内容滑到顶部 → 切回 Pan 拖拽(用户下拉时 velocity > 0) + if pan.velocity(in: self).y > 0 || groupMemberView.frame.minY > groupMemberUpLimit + 1 { + isSubCanScroll = false + panStartTop = groupMemberView.frame.minY + } + } else { + // GroupMemberView 在顶部且继续上滑 → 激活内容滑动 + if groupMemberView.frame.minY <= groupMemberUpLimit && newTop <= groupMemberUpLimit { + isSubCanScroll = true + panStartTop = groupMemberView.frame.minY + topConstraint.constant = groupMemberUpLimit - groupMemberDownLimit + 10 return } - // 到达顶部继续下拉 → 关闭子滚动,交由 view 的 pan 处理 - isSubCanScroll = false - panStartTop = groupMemberView.frame.minY } let clamped = max(groupMemberUpLimit, min(groupMemberDownLimit, newTop)) diff --git a/QuickLocation/Section/Home/HomeViewController.swift b/QuickLocation/Section/Home/HomeViewController.swift index 001e231..2e8837e 100644 --- a/QuickLocation/Section/Home/HomeViewController.swift +++ b/QuickLocation/Section/Home/HomeViewController.swift @@ -187,6 +187,31 @@ class HomeViewController: BaseViewController { self?.requestGroupInfo() }.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 self?.isMemberPanelShown = false diff --git a/QuickLocation/Section/Home/SearchLocation/SearchLocationResultVC.swift b/QuickLocation/Section/Home/SearchLocation/SearchLocationResultVC.swift new file mode 100644 index 0000000..6bded03 --- /dev/null +++ b/QuickLocation/Section/Home/SearchLocation/SearchLocationResultVC.swift @@ -0,0 +1,124 @@ +// +// 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") + } +} diff --git a/QuickLocation/Section/Home/SearchLocation/SearchLocationResultView.swift b/QuickLocation/Section/Home/SearchLocation/SearchLocationResultView.swift new file mode 100644 index 0000000..138a291 --- /dev/null +++ b/QuickLocation/Section/Home/SearchLocation/SearchLocationResultView.swift @@ -0,0 +1,414 @@ +// +// 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") + } +} diff --git a/QuickLocation/Section/Home/SearchLocation/SearchLocationVC.swift b/QuickLocation/Section/Home/SearchLocation/SearchLocationVC.swift new file mode 100644 index 0000000..0204482 --- /dev/null +++ b/QuickLocation/Section/Home/SearchLocation/SearchLocationVC.swift @@ -0,0 +1,89 @@ +// +// 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) + } +} diff --git a/QuickLocation/Section/Home/SearchLocation/SearchLocationView.swift b/QuickLocation/Section/Home/SearchLocation/SearchLocationView.swift new file mode 100644 index 0000000..673617d --- /dev/null +++ b/QuickLocation/Section/Home/SearchLocation/SearchLocationView.swift @@ -0,0 +1,543 @@ +// +// 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() + 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") + } +} diff --git a/QuickLocation/Section/Login/LoginView.swift b/QuickLocation/Section/Login/LoginView.swift index bb76173..76fcae9 100644 --- a/QuickLocation/Section/Login/LoginView.swift +++ b/QuickLocation/Section/Login/LoginView.swift @@ -322,7 +322,7 @@ class LoginView: UIView { textField.font = .systemFont(ofSize: 16, weight: .medium) textField.placeholderColor(placeholder: "请输入验证码", color: .white) - textField.keyboardType = .numbersAndPunctuation + textField.keyboardType = .numberPad return textField }() diff --git a/QuickLocation/Service/SystemService.swift b/QuickLocation/Service/SystemService.swift index a315c0b..e180024 100644 --- a/QuickLocation/Service/SystemService.swift +++ b/QuickLocation/Service/SystemService.swift @@ -41,4 +41,25 @@ struct SystemService { .map(ResponseModel.self) .asObservable() } + + /// 查找 + /// - Parameters: + /// - op_type: plate_num 车牌 phone 手机号 + /// - number: 对应号码 + static func search(op_type: String, number: String) -> Observable { + 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 { + let api = SystemAPI.phoneArea(phone: phone).multiTarget + return APIProvider.request(token: api) + .map(ResponseModel.self) + .asObservable() + } }