diff --git a/QuickLocation.xcodeproj/project.pbxproj b/QuickLocation.xcodeproj/project.pbxproj index 257271a..4ee2652 100644 --- a/QuickLocation.xcodeproj/project.pbxproj +++ b/QuickLocation.xcodeproj/project.pbxproj @@ -195,6 +195,9 @@ 30A87A6F2FEF7BE40095E7C6 /* SearchLocationResultVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A6E2FEF7BE40095E7C6 /* SearchLocationResultVC.swift */; }; 30A87A712FEF7BED0095E7C6 /* SearchLocationResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A702FEF7BED0095E7C6 /* SearchLocationResultView.swift */; }; 30ACD54C2FF6336000174861 /* PopupWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30ACD54B2FF6336000174861 /* PopupWindow.swift */; }; + 30ACD54E2FF7942E00174861 /* PromotionalActivitiesVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30ACD54D2FF7942E00174861 /* PromotionalActivitiesVC.swift */; }; + 30ACD5502FF7943A00174861 /* PromotionalActivitiesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30ACD54F2FF7943A00174861 /* PromotionalActivitiesView.swift */; }; + 30ACD67B2FF7B36A00174861 /* PopupQueueManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30ACD67A2FF7B36A00174861 /* PopupQueueManager.swift */; }; 30B74B3A2FF2115A00F6744D /* GroupScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B392FF2115A00F6744D /* GroupScheduleView.swift */; }; 30B74B3C2FF2117900F6744D /* GroupScheduleVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B3B2FF2117900F6744D /* GroupScheduleVC.swift */; }; 30B74B412FF2437E00F6744D /* GroupMemberListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B74B402FF2437E00F6744D /* GroupMemberListVC.swift */; }; @@ -490,6 +493,9 @@ 30A87A6E2FEF7BE40095E7C6 /* SearchLocationResultVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationResultVC.swift; sourceTree = ""; }; 30A87A702FEF7BED0095E7C6 /* SearchLocationResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationResultView.swift; sourceTree = ""; }; 30ACD54B2FF6336000174861 /* PopupWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupWindow.swift; sourceTree = ""; }; + 30ACD54D2FF7942E00174861 /* PromotionalActivitiesVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromotionalActivitiesVC.swift; sourceTree = ""; }; + 30ACD54F2FF7943A00174861 /* PromotionalActivitiesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromotionalActivitiesView.swift; sourceTree = ""; }; + 30ACD67A2FF7B36A00174861 /* PopupQueueManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupQueueManager.swift; sourceTree = ""; }; 30B74B392FF2115A00F6744D /* GroupScheduleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupScheduleView.swift; sourceTree = ""; }; 30B74B3B2FF2117900F6744D /* GroupScheduleVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupScheduleVC.swift; sourceTree = ""; }; 30B74B402FF2437E00F6744D /* GroupMemberListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberListVC.swift; sourceTree = ""; }; @@ -1344,7 +1350,10 @@ 30ACD54A2FF6332200174861 /* PopupWindow */ = { isa = PBXGroup; children = ( + 30ACD67A2FF7B36A00174861 /* PopupQueueManager.swift */, 30ACD54B2FF6336000174861 /* PopupWindow.swift */, + 30ACD54D2FF7942E00174861 /* PromotionalActivitiesVC.swift */, + 30ACD54F2FF7943A00174861 /* PromotionalActivitiesView.swift */, ); path = PopupWindow; sourceTree = ""; @@ -1805,6 +1814,7 @@ 305A76912FCA8C7000227D26 /* Constant.swift in Sources */, 305A76922FCA8C7000227D26 /* LogUtils.swift in Sources */, 305A76932FCA8C7000227D26 /* AddImageCell.swift in Sources */, + 30ACD54E2FF7942E00174861 /* PromotionalActivitiesVC.swift in Sources */, 30EFF3D82FDA8F1000EB35D4 /* EmergencyContactVC.swift in Sources */, 30A87A682FEE86560095E7C6 /* CreateBubblePopView.swift in Sources */, 30A87A4B2FEE1DAB0095E7C6 /* ItineraryDetailVC.swift in Sources */, @@ -1836,6 +1846,7 @@ 30D87D052FE1336300E958FD /* NavigationView.swift in Sources */, 30B819F42FF3CE4900FAB693 /* ItineraryTraceVC.swift in Sources */, 305A76A02FCA8C7000227D26 /* TextTableViewCell.swift in Sources */, + 30ACD67B2FF7B36A00174861 /* PopupQueueManager.swift in Sources */, 305A76A12FCA8C7000227D26 /* UIButton+RTL.m in Sources */, 30EFF3A62FD7C5AF00EB35D4 /* GroupSettingVC.swift in Sources */, 305A76A22FCA8C7000227D26 /* Array+Extension.swift in Sources */, @@ -2041,6 +2052,7 @@ 305A770C2FCA8C7000227D26 /* PageView.swift in Sources */, 305A770D2FCA8C7000227D26 /* PageViewManager.swift in Sources */, 30D891F52FE22E0600E958FD /* OrderAPI.swift in Sources */, + 30ACD5502FF7943A00174861 /* PromotionalActivitiesView.swift in Sources */, 307073EA2FD2715A004C37CC /* GroupChatViewModel.swift in Sources */, 305A770E2FCA8C7000227D26 /* DLAlertPopVC.swift in Sources */, 305A770F2FCA8C7000227D26 /* DLCustomPopVC.swift in Sources */, diff --git a/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate b/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate index 8c6ceb2..72a841c 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/Assets.xcassets/PopupWindow/vip2_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/PopupWindow/vip2_bg.imageset/Contents.json new file mode 100644 index 0000000..b774b73 --- /dev/null +++ b/QuickLocation/Assets.xcassets/PopupWindow/vip2_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_2092@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_2092@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_bg.imageset/Group_2092@2x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip2_bg.imageset/Group_2092@2x.png new file mode 100644 index 0000000..3bea353 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip2_bg.imageset/Group_2092@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_bg.imageset/Group_2092@3x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip2_bg.imageset/Group_2092@3x.png new file mode 100644 index 0000000..9ef4a48 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip2_bg.imageset/Group_2092@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_icon.imageset/Contents.json b/QuickLocation/Assets.xcassets/PopupWindow/vip2_icon.imageset/Contents.json new file mode 100644 index 0000000..197fb60 --- /dev/null +++ b/QuickLocation/Assets.xcassets/PopupWindow/vip2_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Mask_group@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Mask_group@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_icon.imageset/Mask_group@2x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip2_icon.imageset/Mask_group@2x.png new file mode 100644 index 0000000..cd07c11 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip2_icon.imageset/Mask_group@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_icon.imageset/Mask_group@3x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip2_icon.imageset/Mask_group@3x.png new file mode 100644 index 0000000..c3ef117 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip2_icon.imageset/Mask_group@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_logo.imageset/Contents.json b/QuickLocation/Assets.xcassets/PopupWindow/vip2_logo.imageset/Contents.json new file mode 100644 index 0000000..e3110d0 --- /dev/null +++ b/QuickLocation/Assets.xcassets/PopupWindow/vip2_logo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group 2091@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group 2091@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_logo.imageset/Group 2091@2x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip2_logo.imageset/Group 2091@2x.png new file mode 100644 index 0000000..ebcbd0d Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip2_logo.imageset/Group 2091@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_logo.imageset/Group 2091@3x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip2_logo.imageset/Group 2091@3x.png new file mode 100644 index 0000000..4555e78 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip2_logo.imageset/Group 2091@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_text.imageset/Contents.json b/QuickLocation/Assets.xcassets/PopupWindow/vip2_text.imageset/Contents.json new file mode 100644 index 0000000..99168a4 --- /dev/null +++ b/QuickLocation/Assets.xcassets/PopupWindow/vip2_text.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "vip2_text@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "vip2_text@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_text.imageset/vip2_text@2x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip2_text.imageset/vip2_text@2x.png new file mode 100644 index 0000000..3a0f0e1 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip2_text.imageset/vip2_text@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_text.imageset/vip2_text@3x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip2_text.imageset/vip2_text@3x.png new file mode 100644 index 0000000..eb5c381 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip2_text.imageset/vip2_text@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_text_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/PopupWindow/vip2_text_bg.imageset/Contents.json new file mode 100644 index 0000000..dd6b162 --- /dev/null +++ b/QuickLocation/Assets.xcassets/PopupWindow/vip2_text_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_2084@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_2084@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_text_bg.imageset/Group_2084@2x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip2_text_bg.imageset/Group_2084@2x.png new file mode 100644 index 0000000..e001b1c Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip2_text_bg.imageset/Group_2084@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip2_text_bg.imageset/Group_2084@3x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip2_text_bg.imageset/Group_2084@3x.png new file mode 100644 index 0000000..686b795 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip2_text_bg.imageset/Group_2084@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip3_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/PopupWindow/vip3_bg.imageset/Contents.json new file mode 100644 index 0000000..a6a2dc4 --- /dev/null +++ b/QuickLocation/Assets.xcassets/PopupWindow/vip3_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_2094@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_2094@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip3_bg.imageset/Group_2094@2x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip3_bg.imageset/Group_2094@2x.png new file mode 100644 index 0000000..d4d8cf3 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip3_bg.imageset/Group_2094@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip3_bg.imageset/Group_2094@3x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip3_bg.imageset/Group_2094@3x.png new file mode 100644 index 0000000..b8333b8 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip3_bg.imageset/Group_2094@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip4_animate.imageset/Contents.json b/QuickLocation/Assets.xcassets/PopupWindow/vip4_animate.imageset/Contents.json new file mode 100644 index 0000000..aba566c --- /dev/null +++ b/QuickLocation/Assets.xcassets/PopupWindow/vip4_animate.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group 2109@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group 2109@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip4_animate.imageset/Group 2109@2x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip4_animate.imageset/Group 2109@2x.png new file mode 100644 index 0000000..4afa71b Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip4_animate.imageset/Group 2109@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip4_animate.imageset/Group 2109@3x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip4_animate.imageset/Group 2109@3x.png new file mode 100644 index 0000000..67a8bda Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip4_animate.imageset/Group 2109@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip4_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/PopupWindow/vip4_bg.imageset/Contents.json new file mode 100644 index 0000000..96e76ac --- /dev/null +++ b/QuickLocation/Assets.xcassets/PopupWindow/vip4_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "vip4_bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "vip4_bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip4_bg.imageset/vip4_bg@2x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip4_bg.imageset/vip4_bg@2x.png new file mode 100644 index 0000000..5f955b8 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip4_bg.imageset/vip4_bg@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PopupWindow/vip4_bg.imageset/vip4_bg@3x.png b/QuickLocation/Assets.xcassets/PopupWindow/vip4_bg.imageset/vip4_bg@3x.png new file mode 100644 index 0000000..fee9a28 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PopupWindow/vip4_bg.imageset/vip4_bg@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/Contents.json b/QuickLocation/Assets.xcassets/PromotionalActivities/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/QuickLocation/Assets.xcassets/PromotionalActivities/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/benefits.imageset/Contents.json b/QuickLocation/Assets.xcassets/PromotionalActivities/benefits.imageset/Contents.json new file mode 100644 index 0000000..3fba2a2 --- /dev/null +++ b/QuickLocation/Assets.xcassets/PromotionalActivities/benefits.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "benefits@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "benefits@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/benefits.imageset/benefits@2x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/benefits.imageset/benefits@2x.png new file mode 100644 index 0000000..9069f95 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/benefits.imageset/benefits@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/benefits.imageset/benefits@3x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/benefits.imageset/benefits@3x.png new file mode 100644 index 0000000..9e5932a Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/benefits.imageset/benefits@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/header_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/PromotionalActivities/header_bg.imageset/Contents.json new file mode 100644 index 0000000..88b0b05 --- /dev/null +++ b/QuickLocation/Assets.xcassets/PromotionalActivities/header_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "banner@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "banner@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/header_bg.imageset/banner@2x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/header_bg.imageset/banner@2x.png new file mode 100644 index 0000000..6de1491 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/header_bg.imageset/banner@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/header_bg.imageset/banner@3x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/header_bg.imageset/banner@3x.png new file mode 100644 index 0000000..bdc56e8 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/header_bg.imageset/banner@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/vip_icon.imageset/Contents.json b/QuickLocation/Assets.xcassets/PromotionalActivities/vip_icon.imageset/Contents.json new file mode 100644 index 0000000..af96747 --- /dev/null +++ b/QuickLocation/Assets.xcassets/PromotionalActivities/vip_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_2426@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_2426@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/vip_icon.imageset/Group_2426@2x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/vip_icon.imageset/Group_2426@2x.png new file mode 100644 index 0000000..0664df3 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/vip_icon.imageset/Group_2426@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/vip_icon.imageset/Group_2426@3x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/vip_icon.imageset/Group_2426@3x.png new file mode 100644 index 0000000..e7f6d63 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/vip_icon.imageset/Group_2426@3x.png differ diff --git a/QuickLocation/Info.plist b/QuickLocation/Info.plist index 2aebdac..ef187a1 100644 --- a/QuickLocation/Info.plist +++ b/QuickLocation/Info.plist @@ -43,6 +43,7 @@ UIAppFonts douyu.otf + zihunbiantaoti-r.ttf diff --git a/QuickLocation/Manager/Account/UserConfigModel.swift b/QuickLocation/Manager/Account/UserConfigModel.swift index 71958e4..656b26a 100644 --- a/QuickLocation/Manager/Account/UserConfigModel.swift +++ b/QuickLocation/Manager/Account/UserConfigModel.swift @@ -63,6 +63,12 @@ struct UserConfigModel: Mappable { } struct SystemConfigModel: Mappable { + + /// 弹窗配置 + var popupConfig: PopupWindowConfigModel? + /// 弹窗时间配置 + var timeConfig: PopupWindowTimeModel? + /// 聊天界面的警告说明 var chatWarning: String = "" @@ -76,5 +82,50 @@ struct SystemConfigModel: Mappable { mutating func mapping(map: Map) { chatWarning <- map["client.chatwarning", nested: false] groupBannerList <- map["client.team.ad", nested: false] + popupConfig <- map["client.popup.display", nested: false] + timeConfig <- map["client.time.setting", nested: false] + } +} + +struct PopupWindowConfigModel: Mappable { + /// 是否弹vip弹窗 + var pay_pop: Bool = false + + /// 搜索位置弹窗 + var search_phone: Bool = false + + + init?(map: Map) { + + } + + mutating func mapping(map: Map) { + pay_pop <- map["pay_pop"] + search_phone <- map["search_phone"] + } +} + +struct PopupWindowTimeModel: Mappable { + /// 会员充值push倒计时 + var buy_vip_tip: Int = 0 + /// 搜索位置 倒计时(秒) + var search_phone: Int = 0 + /// 注册提示 + var signup_tip: Int = 0 + /// vip提示倒计时(秒) + var pay_pop_time: Int = 0 + /// vip折扣 + var min_discount: Double = 0 + + init?(map: Map) { + + } + + mutating func mapping(map: Map) { + buy_vip_tip <- map["buy_vip_tip"] + search_phone <- map["search_phone"] + signup_tip <- map["signup_tip"] + pay_pop_time <- map["pay_pop_time"] + min_discount <- map["min_discount"] } } diff --git a/QuickLocation/Manager/App/RouterManager.swift b/QuickLocation/Manager/App/RouterManager.swift index 0420fe3..10130f5 100644 --- a/QuickLocation/Manager/App/RouterManager.swift +++ b/QuickLocation/Manager/App/RouterManager.swift @@ -75,6 +75,8 @@ enum Route: String { case groupMemberList = "groupMemberList" /// 行程轨迹 case itineraryTrace = "itineraryTrace" + /// 充值优惠弹窗 + case promotionalActivities = "promotionalActivities" } extension Route: RouterTarget { @@ -346,6 +348,11 @@ extension AppRouter: AppRouterProtocol { ItineraryTraceVC(scheduleJson: parameters["model"].safeDictionary as! [String : Any], memberInfo: parameters["memberInfo"].safeDictionary as! [String : Any]) } + + // MARK: - 充值优惠弹窗 + AppRouter.register(Route.promotionalActivities) { url, parameters in + PromotionalActivitiesVC() + } } } diff --git a/QuickLocation/Section/Home/HomeViewController.swift b/QuickLocation/Section/Home/HomeViewController.swift index f4a16c6..6708221 100644 --- a/QuickLocation/Section/Home/HomeViewController.swift +++ b/QuickLocation/Section/Home/HomeViewController.swift @@ -31,9 +31,9 @@ class HomeViewController: BaseViewController { private var tableView: UITableView { rootView.groupMemberView.tableView } - + private var viewModel = HomeViewModel() - + private let locationManager = CLLocationManager() private var currentHeading: Double = 0 private var members: [CircleMember] = [] @@ -50,11 +50,12 @@ class HomeViewController: BaseViewController { private var lastUpdateTimes: [String: Date] = [:] /// 离线检测定时器 private var offlineCheckTimer: Timer? + private var popupQueue: PopupQueueManager? private var sosPlayerKey: UInt8 = 0 private var groupRefreshWorkItem: DispatchWorkItem? private var groupSwitchWorkItem: DispatchWorkItem? - + override func loadView() { #if !targetEnvironment(simulator) MAMapView.updatePrivacyAgree(.didAgree) @@ -73,16 +74,16 @@ class HomeViewController: BaseViewController { reactiveAction() requestUserConfig() - + rootView.quickMessageView.tagListView.delegate = self // MQTT 位置上报 UIDevice.current.isBatteryMonitoringEnabled = true startLocationTimer() } - + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - PopupWindow.show(popupType: .register) + } // MARK: - MQTT 位置上报 @@ -129,34 +130,34 @@ class HomeViewController: BaseViewController { accuracy: loc.horizontalAccuracy ) } - + // MARK: - Actions private func reactiveAction() { // 气泡 rootView.bubbleView.rx.tapGesture.subscribe { _ in AppRouter.push(Route.createBubble) }.disposed(by: disposeBag) - + // 签到 rootView.signInView.rx.tapGesture.subscribe { _ in let vc = SignInVC(lastLocation: self.lastLocation) vc.isNeedLogin = true AppRouter.push(vc) }.disposed(by: disposeBag) - + // SOS rootView.sosView.rx.tapGesture.subscribe { _ in let vc = SOSViewController() vc.isNeedLogin = true AppRouter.push(vc) }.disposed(by: disposeBag) - + // 行程 rootView.scheduleView.rx.tapGesture.subscribe { _ in guard let model = self.viewModel.groupModel else { return } AppRouter.push(Route.groupSchedule, userInfo: ["groupKey": model.default_group_key]) }.disposed(by: disposeBag) - + // 顶部圈子 rootView.groupView.rx.tapGesture.subscribe { _ in guard let groupModel = self.viewModel.groupModel else { return } @@ -168,36 +169,36 @@ class HomeViewController: BaseViewController { self.requestOperateGroup(groupKey: key) } }.disposed(by: disposeBag) - + // 圈子成员列表 刷新列表 rootView.groupMemberView.refreshBtn.rx.tap.subscribe(onNext: { _ in self.requestGroupInfo() }).disposed(by: disposeBag) - + // 圈子成员列表 邀请加入 rootView.groupMemberView.inviteJoinBtn.rx.tap.subscribe(onNext: { _ in AppRouter.push(Route.inviteJoin, userInfo: ["groupInfo": self.viewModel.groupInfo]) }).disposed(by: disposeBag) - + // 地图回到自己 rootView.locationView.rx.tapGesture.subscribe { _ in if let ann = self.currentUserAnnotation { self.rootView.mapView.setCenter(ann.coordinate, animated: true) } }.disposed(by: disposeBag) - + // User Config刷新 NotificationCenter.default.rx.notification(.RefreshUserConfigNotification, object: nil) .subscribe { [weak self] notification in self?.requestUserConfig() }.disposed(by: disposeBag) - + // 圈子刷新 NotificationCenter.default.rx.notification(.RefreshGroupInfoNotification, object: nil) .subscribe { [weak self] notification in self?.requestGroupInfo() }.disposed(by: disposeBag) - + // 查找位置(查看成员位置) NotificationCenter.default.rx.notification(.ShowMemberLocationNotification, object: nil) .subscribe { [weak self] notification in @@ -222,7 +223,7 @@ class HomeViewController: BaseViewController { } }.disposed(by: self.disposeBag) }.disposed(by: disposeBag) - + // 面板关闭回调 rootView.onDismissPanel = { [weak self] in self?.isMemberPanelShown = false @@ -246,12 +247,12 @@ class HomeViewController: BaseViewController { self.requestSendEmote(emoteIdx: emoteIdx) } } - + private func bindViewModel() { viewModel.output.sectionedItems .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag) - + let selectedMember = tableView.rx.modelSelected(GroupMemberModel.self) .share() @@ -267,7 +268,7 @@ class HomeViewController: BaseViewController { }) .disposed(by: disposeBag) } - + // MARK: - UITableViewDataSource lazy private var dataSource: RxTableViewSectionedReloadDataSource = { return RxTableViewSectionedReloadDataSource( @@ -281,7 +282,7 @@ class HomeViewController: BaseViewController { }() // MARK: - API - + /// 获取用户配置 func requestUserConfig() { SystemService.userConfig().subscribe(onNext: { response in @@ -295,9 +296,12 @@ class HomeViewController: BaseViewController { } // 首页公告 self.requestNotice() + // 弹窗控制器 + self.popupQueue = PopupQueueManager() + self.popupQueue?.start(from: self) }).disposed(by: disposeBag) } - + /// 获取用户IM Token func getUserIMToken() { DLToast.showLoading() @@ -309,7 +313,7 @@ class HomeViewController: BaseViewController { } }).disposed(by: disposeBag) } - + private func requestUserInfo(completion: (() -> Void)? = nil) { UserService.userInfo().subscribe { response in guard let model = response.model else { return } @@ -318,13 +322,13 @@ class HomeViewController: BaseViewController { completion?() }.disposed(by: disposeBag) } - + private func requestGroupInfo(isDefaultGroup: Bool=true) { GroupService.groupInfo().subscribe { response in guard let model = response.model else { return } self.viewModel.groupModel = model NotificationCenter.default.post(name: .RefreshIMGroupListNotification, object: nil) - + guard isDefaultGroup else { return } self.rootView.groupMemberView.setupCountData(self.viewModel.memberCount, 1) self.rootView.groupNameLab.text = self.viewModel.groupName @@ -332,13 +336,13 @@ class HomeViewController: BaseViewController { self.refreshMQTTSubscriptions(model.select_group_employee) }.disposed(by: disposeBag) } - + private func requestOperateGroup(groupKey: String) { GroupService.operate(opType: "setdefault", requestData: ["group_key" : groupKey]).subscribe { response in self.requestGroupInfo() }.disposed(by: disposeBag) } - + private func requestNotice() { UserService.notice().subscribe { response in self.rootView.noticeLab.text = "" @@ -354,7 +358,7 @@ class HomeViewController: BaseViewController { } }.disposed(by: disposeBag) } - + // MARK: - Map Setup private func setupMap() { #if !targetEnvironment(simulator) @@ -429,7 +433,7 @@ class HomeViewController: BaseViewController { if let currentModel = list.first(where: { $0.user_id == AppContextManager.shared.userId }) { lastUpdateTime = currentModel.last_active_time } - + // 当前用户始终由 GPS 定位,单独注入 let me = CircleMember( id: "current", @@ -459,7 +463,7 @@ class HomeViewController: BaseViewController { let onlineMembers = newMembers.filter { $0.isOnline } guard let mapView = rootView.mapView else { return } - + let existing = mapView.annotations?.compactMap { $0 as? MemberAnnotation } ?? [] let existingIDs = Set(existing.map { $0.member.id }) let onlineIDs = Set(onlineMembers.map { $0.id }) @@ -531,13 +535,13 @@ extension HomeViewController { else { return } let userId = topic.replacingOccurrences(of: "smartdrive/", with: "") - + switch msg.type { case "track": guard let firstPoint = msg.data?.points?.first else { return } let coord = CLLocationCoordinate2D(latitude: firstPoint.lat, longitude: firstPoint.lon) guard CLLocationCoordinate2DIsValid(coord) else { return } - + // 根据 time 字段判断在线状态 let nowMs = Date().timeIntervalSince1970 * 1000 let msgTimeMs = Double(firstPoint.time) @@ -583,7 +587,7 @@ extension HomeViewController { let lastChar = String(index.string.suffix(index.string.count-1)) let parts = gk.components(separatedBy: "/") let emoteUserId = parts[1] - + if firstChar == "1" || firstChar == "2" { // 表情 let emojiFileName = firstChar == "1" ? "normal_" : "fun_" let nickName = self.viewModel.getUserNickName(id: emoteUserId) @@ -686,7 +690,7 @@ extension HomeViewController { else { debounceGroupRefresh() } - + default: print("📩 未处理 type=\(msg.type ?? "")") } @@ -724,7 +728,7 @@ extension HomeViewController { battery: old.battery ) } - + // 更新地图标注 guard let mapView = rootView.mapView else { return } if let oldMember = members.first(where: { $0.id == userId }), oldMember.isOnline { @@ -773,11 +777,11 @@ extension HomeViewController: MAMapViewDelegate { func mapView(_ mapView: MAMapView!, didUpdate userLocation: MAUserLocation!, updatingLocation: Bool) { guard updatingLocation, let location = userLocation.location else { return } - + // 卫星信号 let strength = gpsSignalStrength(from: location) rootView.updateGPSSignal(bars: strength.barCount) - + // 地图标注 lastLocation = location Defaults[\.currentLatitude] = location.coordinate.latitude @@ -808,7 +812,7 @@ extension HomeViewController { // 停止上一次报警 sosPlayer?.stop() sosPlayer = nil - + guard let url = Bundle.main.url(forResource: "sos", withExtension: "mp3") ?? Bundle.main.url(forResource: "sos", withExtension: "mp3", subdirectory: "sound") else { print("❌ SOS: sos.mp3 not found in bundle") return @@ -841,7 +845,7 @@ extension HomeViewController: TTGTextTagCollectionViewDelegate { let emoteIdx = "3\(index)".integer requestSendEmote(emoteIdx: emoteIdx) } - + func requestSendEmote(emoteIdx: Int) { guard let model = viewModel.groupModel else { return } DLToast.showLoading() diff --git a/QuickLocation/Section/Mine/MineViewModel.swift b/QuickLocation/Section/Mine/MineViewModel.swift index 1788780..5e2e25b 100644 --- a/QuickLocation/Section/Mine/MineViewModel.swift +++ b/QuickLocation/Section/Mine/MineViewModel.swift @@ -26,7 +26,7 @@ class MineViewModel { let menuItems: [MineMenuItem] = [ MineMenuItem(icon: "Mine/account_security", title: "账户与安全"), - MineMenuItem(icon: "Mine/status_setting", title: "状态设置"), +// MineMenuItem(icon: "Mine/status_setting", title: "状态设置"), MineMenuItem(icon: "Mine/emergency_contact", title: "紧急联系人"), MineMenuItem(icon: "Mine/customer_service", title: "在线客服"), MineMenuItem(icon: "Mine/privacy_agreement", title: "隐私与协议"), diff --git a/QuickLocation/Section/PopupWindow/PopupQueueManager.swift b/QuickLocation/Section/PopupWindow/PopupQueueManager.swift new file mode 100644 index 0000000..4f23b2c --- /dev/null +++ b/QuickLocation/Section/PopupWindow/PopupQueueManager.swift @@ -0,0 +1,145 @@ +// +// PopupQueueManager.swift +// QuickLocation +// +// Created by 八条 on 2026/7/3. +// + +import UIKit + +class PopupQueueManager { + + private weak var sourceVC: UIViewController? + private var currentStep = 0 + private var countdownTimer: Timer? + + func start(from vc: UIViewController) { + sourceVC = vc + currentStep = 0 + executeNextStep() + } + + private func executeNextStep() { + switch currentStep { + case 0: + tryStartPromotionalActivities() + case 1: + tryStartSearchPopup() + case 2: + tryStartRegisterOrVipPopup() + default: + break + } + } + + // MARK: - Step 0: PromotionalActivities + + private func tryStartPromotionalActivities() { + guard let config = AppContextManager.shared.systemConfig else { + skipToNext() + return + } + guard config.popupConfig?.pay_pop == true, + AppContextManager.shared.vip == 1, + let timeConfig = config.timeConfig else { + skipToNext() + return + } + startCountdown(seconds: timeConfig.pay_pop_time) { [weak self] in + let vc = PromotionalActivitiesVC() + vc.onDismiss = { [weak self] in + self?.skipToNext() + } + self?.push(vc) + } + } + + // MARK: - Step 1: Search PopupWindow + + private func tryStartSearchPopup() { + guard let config = AppContextManager.shared.systemConfig else { + skipToNext() + return + } + guard config.popupConfig?.search_phone == true, + let timeConfig = config.timeConfig else { + skipToNext() + return + } + startCountdown(seconds: timeConfig.search_phone) { [weak self] in + PopupWindow.show(popupType: .search) { + self?.skipToNext() + } + } + } + + // MARK: - Step 2: Register or VIP PopupWindow (mutually exclusive) + + private func tryStartRegisterOrVipPopup() { + guard let config = AppContextManager.shared.systemConfig, + let timeConfig = config.timeConfig else { + return + } + + if AppContextManager.shared.isGuest { + startCountdown(seconds: timeConfig.signup_tip) { [weak self] in + PopupWindow.show(popupType: .register) { + // done + } + } + } else if AppContextManager.shared.vip == 1 { + startCountdown(seconds: timeConfig.buy_vip_tip) { [weak self] in + PopupWindow.show(popupType: .vip) { + // done + } + } + } + } + + // MARK: - Helpers + + private func skipToNext() { + currentStep += 1 + executeNextStep() + } + + private func startCountdown(seconds: Int, onExpired: @escaping () -> Void) { + guard seconds > 0 else { + onExpired() + return + } + countdownTimer?.invalidate() + var remaining = seconds + countdownTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in + remaining -= 1 + if remaining <= 0 { + timer.invalidate() + self?.countdownTimer = nil + onExpired() + } + } + } + + private func push(_ vc: UIViewController) { + let topVC = topViewController() + topVC?.navigationController?.pushViewController(vc, animated: true) + } + + private func topViewController() -> UIViewController? { + guard let rootVC = kKeyWindow?.rootViewController else { return nil } + return topViewController(from: rootVC) + } + + private func topViewController(from root: UIViewController) -> UIViewController { + if let presented = root.presentedViewController { + return topViewController(from: presented) + } + if let nav = root as? UINavigationController, let visible = nav.visibleViewController { + return topViewController(from: visible) + } + if let tab = root as? UITabBarController, let selected = tab.selectedViewController { + return topViewController(from: selected) + } + return root + } +} diff --git a/QuickLocation/Section/PopupWindow/PopupWindow.swift b/QuickLocation/Section/PopupWindow/PopupWindow.swift index f28baae..7495636 100644 --- a/QuickLocation/Section/PopupWindow/PopupWindow.swift +++ b/QuickLocation/Section/PopupWindow/PopupWindow.swift @@ -42,14 +42,43 @@ class PopupWindow: UIView { slideInRegisterPopup() } case .vip: - slideInUnlockVip1Popup() + let roll = Int.random(in: 0...3) + switch roll { + case 0: + unlockVip2View.isHidden = true + unlockVip3View.isHidden = true + unlockVip4View.isHidden = true + unlockVip1View.isHidden = false + slideInUnlockVip1Popup() + case 1: + unlockVip1View.isHidden = true + unlockVip3View.isHidden = true + unlockVip4View.isHidden = true + unlockVip2View.isHidden = false + slideInUnlockVip2Popup() + case 2: + unlockVip1View.isHidden = true + unlockVip2View.isHidden = true + unlockVip4View.isHidden = true + unlockVip3View.isHidden = false + slideInUnlockVip3Popup() + default: + unlockVip1View.isHidden = true + unlockVip2View.isHidden = true + unlockVip3View.isHidden = true + unlockVip4View.isHidden = false + slideInUnlockVip4Popup() + } default: break } } } - static func show(popupType: PopupType) { + private static var dismissCompletion: (() -> Void)? + + static func show(popupType: PopupType, dismissCompletion: (() -> Void)? = nil) { + Self.dismissCompletion = dismissCompletion guard let superView = kKeyWindow else { return } @@ -76,9 +105,15 @@ class PopupWindow: UIView { stopRegisterStarFlash() stopRegister1LottieAlternate() stopUnlockVip1Animations() + stopUnlockVip2Animations() + stopUnlockVip3Animations() + stopUnlockVip4Animations() registerPopupView.transform = .identity register2PopupView.transform = .identity unlockVip1View.transform = .identity + unlockVip2View.transform = .identity + unlockVip3View.transform = .identity + unlockVip4View.transform = .identity registerStar1.transform = .identity // register2Star1.transform = .identity @@ -86,6 +121,9 @@ class PopupWindow: UIView { registerPopupView.isHidden = true register2PopupView.isHidden = true unlockVip1View.isHidden = true + unlockVip2View.isHidden = true + unlockVip3View.isHidden = true + unlockVip4View.isHidden = true bgView.isUserInteractionEnabled = true } @@ -98,6 +136,9 @@ class PopupWindow: UIView { } completion: { _ in PopupWindow.shared.resetPopupState() PopupWindow.shared.removeFromSuperview() + let completion = Self.dismissCompletion + Self.dismissCompletion = nil + completion?() } } @@ -139,6 +180,42 @@ class PopupWindow: UIView { self?.slideOutUnlockVip1Popup() }) .disposed(by: disposeBag) + + unlockVip2CloseBtn.rx.tap + .subscribe(onNext: { [weak self] _ in + self?.slideOutUnlockVip2Popup() + }) + .disposed(by: disposeBag) + + unlockVip2Btn.rx.tap + .subscribe(onNext: { [weak self] _ in + self?.slideOutUnlockVip2Popup() + }) + .disposed(by: disposeBag) + + unlockVip3CloseBtn.rx.tap + .subscribe(onNext: { [weak self] _ in + self?.slideOutUnlockVip3Popup() + }) + .disposed(by: disposeBag) + + unlockVip3Btn.rx.tap + .subscribe(onNext: { [weak self] _ in + self?.slideOutUnlockVip3Popup() + }) + .disposed(by: disposeBag) + + unlockVip4CloseBtn.rx.tap + .subscribe(onNext: { [weak self] _ in + self?.slideOutUnlockVip4Popup() + }) + .disposed(by: disposeBag) + + unlockVip4Btn.rx.tap + .subscribe(onNext: { [weak self] _ in + self?.slideOutUnlockVip4Popup() + }) + .disposed(by: disposeBag) } private lazy var bgView: UIView = { @@ -155,6 +232,10 @@ class PopupWindow: UIView { let bgImgView = UIImageView(image: UIImage(named: "PopupWindow/search_bg")) bgImgView.contentMode = .scaleAspectFill + bgImgView.rx.tapGesture.subscribe(onNext: { _ in + PopupWindow.dismiss() + AppRouter.push(Route.searchLocation) + }).disposed(by: disposeBag) view.addSubview(bgImgView) bgImgView.layoutChain .edges(excludingEdge: .bottom) @@ -270,7 +351,7 @@ class PopupWindow: UIView { view.addSubview(vip1CloseBtn) vip1CloseBtn.layoutChain - .topToView(bgImg, offset: 32) + .topToView(bgImg, offset: 16) .right(17) view.addSubview(vip1UnlockBtn) @@ -392,6 +473,475 @@ class PopupWindow: UIView { } } + // MARK: - 解锁会员 2 + private let unlockVip2PopupHeight: CGFloat = 412 + + lazy var unlockVip2View: UIView = { + let view = UIView() + view.backgroundColor = .clear + view.isHidden = true + + let bgImg = UIImageView(image: UIImage(named: "PopupWindow/vip2_bg")) + bgImg.contentMode = .scaleAspectFill + view.addSubview(bgImg) + bgImg.layoutChain + .top(84) + .edgesHorzontal() + .bottom() + + let titleLab = UILabel() + let text = "会员升级人次" + let attr = NSMutableAttributedString(string: text) + let strokeAttrs: [NSAttributedString.Key: Any] = [ + .strokeWidth: -5, + .strokeColor: UIColor.black.cgColor, + .foregroundColor: UIColor.white, + .font: UIFont(name: "zihunbiantaoti", size: 20) ?? .systemFont(ofSize: 20) + ] + attr.setAttributes(strokeAttrs, range: NSRange(location: 0, length: text.count)) + titleLab.attributedText = attr + + view.addSubview(titleLab) + titleLab.layoutChain + .top(106) + .left(29) + + let countTitleLab = UILabel() + let countText = "已突破" + let countAttr = NSMutableAttributedString(string: countText) + let countStrokeAttrs: [NSAttributedString.Key: Any] = [ + .strokeWidth: -5, + .strokeColor: UIColor.black.cgColor, + .foregroundColor: UIColor.white, + .font: UIFont(name: "zihunbiantaoti", size: 22) ?? .systemFont(ofSize: 22) + ] + countAttr.setAttributes(countStrokeAttrs, range: NSRange(location: 0, length: countText.count)) + countTitleLab.attributedText = countAttr + view.addSubview(countTitleLab) + countTitleLab.layoutChain + .topToBottomOfView(titleLab, offset: 22) + .leftToView(titleLab) + + view.addSubview(unlockVip2CountLab) + unlockVip2CountLab.layoutChain + .leftToRightOfView(countTitleLab) + .bottomToView(countTitleLab, offset: 3) + + view.addSubview(unlockVip2LogoImg) + unlockVip2LogoImg.layoutChain + .top() + .right() + .width(178) + .height(199) + + let textImg = UIImageView(image: UIImage(named: "PopupWindow/vip2_text")) + view.addSubview(textImg) + textImg.layoutChain + .topToBottomOfView(unlockVip2LogoImg, offset: 15) + .centerX() + .width(275) + .height(41) + + view.addSubview(unlockVip2CloseBtn) + unlockVip2CloseBtn.layoutChain + .bottom(kSafeBottomMargin + 15) + .left(30) + .height(50) + + view.addSubview(unlockVip2Btn) + unlockVip2Btn.layoutChain + .bottomToView(unlockVip2CloseBtn) + .right(30) + .height(50) + .widthToView(unlockVip2CloseBtn) + + unlockVip2CloseBtn.layoutChain.rightToLeftOfView(unlockVip2Btn, offset: -10) + + return view + }() + + lazy var unlockVip2LogoImg: UIImageView = { + let view = UIImageView() + view.image = UIImage(named: "PopupWindow/vip2_logo") + view.contentMode = .scaleAspectFill + return view + }() + + private var unlockVip2CountTarget: Int = 0 + private var unlockVip2CountTimer: Timer? + + lazy var unlockVip2CountLab: UILabel = { + let label = UILabel() + unlockVip2CountTarget = Int.random(in: 10000...15000) + let text = "0+" + let attr = NSMutableAttributedString(string: text) + let strokeAttrs: [NSAttributedString.Key: Any] = [ + .strokeWidth: -5, + .strokeColor: UIColor.black.cgColor, + .foregroundColor: UIColor(hexStr: "#7CE7FC"), + .font: UIFont(name: "zihunbiantaoti", size: 34) ?? .systemFont(ofSize: 34) + ] + attr.setAttributes(strokeAttrs, range: NSRange(location: 0, length: text.count)) + label.attributedText = attr + return label + }() + + lazy var unlockVip2CloseBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setTitle("稍后再说", for: .normal) + btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal) + btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) + btn.backgroundColor = .white + btn.cornerRadius = 25 + btn.borderWidth = 0.5 + btn.borderColor = UIColor(hexStr: "#16B3FF") + return btn + }() + + lazy var unlockVip2Btn: 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 + }() + + private func startUnlockVip2Animations() { + stopUnlockVip2Animations() + let animation = CABasicAnimation(keyPath: "transform.rotation.z") + animation.fromValue = -0.08 + animation.toValue = 0.08 + animation.duration = 0.7 + animation.autoreverses = true + animation.repeatCount = .infinity + animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + unlockVip2LogoImg.layer.add(animation, forKey: "unlockVip2LogoSwing") + startUnlockVip2CountAnimation() + } + + private func stopUnlockVip2Animations() { + unlockVip2LogoImg.layer.removeAnimation(forKey: "unlockVip2LogoSwing") + unlockVip2CountTimer?.invalidate() + unlockVip2CountTimer = nil + } + + private func startUnlockVip2CountAnimation() { + unlockVip2CountTimer?.invalidate() + let target = unlockVip2CountTarget + let duration: TimeInterval = 2.0 + let interval: TimeInterval = 0.025 + let totalSteps = Int(duration / interval) + var step = 0 + unlockVip2CountTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] timer in + step += 1 + let progress = min(Double(step) / Double(totalSteps), 1.0) + let eased = 1.0 - pow(1.0 - progress, 3) + let current = Int(Double(target) * eased) + let text = "\(current)+" + let attr = NSMutableAttributedString(string: text) + let strokeAttrs: [NSAttributedString.Key: Any] = [ + .strokeWidth: -3, + .strokeColor: UIColor.black.cgColor, + .foregroundColor: UIColor(hexStr: "#7CE7FC"), + .font: UIFont(name: "zihunbiantaoti", size: 34) ?? .systemFont(ofSize: 34) + ] + attr.setAttributes(strokeAttrs, range: NSRange(location: 0, length: text.count)) + self?.unlockVip2CountLab.attributedText = attr + if progress >= 1.0 { + timer.invalidate() + self?.unlockVip2CountTimer = nil + } + } + } + + private func slideInUnlockVip2Popup() { + stopUnlockVip2Animations() + unlockVip2CountTarget = Int.random(in: 10000...15000) + let attr = NSMutableAttributedString(string: "0+") + let strokeAttrs: [NSAttributedString.Key: Any] = [ + .strokeWidth: -3, + .strokeColor: UIColor.black.cgColor, + .foregroundColor: UIColor(hexStr: "#7CE7FC"), + .font: UIFont(name: "zihunbiantaoti", size: 34) ?? .systemFont(ofSize: 34) + ] + attr.setAttributes(strokeAttrs, range: NSRange(location: 0, length: 2)) + unlockVip2CountLab.attributedText = attr + bgView.isUserInteractionEnabled = false + unlockVip2View.isHidden = false + unlockVip2View.layoutIfNeeded() + unlockVip2View.transform = CGAffineTransform(translationX: 0, y: unlockVip2PopupHeight) + UIView.animate(withDuration: 0.45, delay: 0, + usingSpringWithDamping: 0.8, + initialSpringVelocity: 0.5, + options: .curveEaseOut) { + self.unlockVip2View.transform = .identity + } completion: { _ in + self.bgView.isUserInteractionEnabled = true + self.startUnlockVip2Animations() + } + } + + private func slideOutUnlockVip2Popup() { + stopUnlockVip2Animations() + bgView.isUserInteractionEnabled = false + UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) { + self.unlockVip2View.transform = CGAffineTransform(translationX: 0, y: self.unlockVip2PopupHeight) + } completion: { _ in + Self.dismiss() + } + } + + // MARK: - 解锁会员 3 + private let unlockVip3PopupHeight: CGFloat = 385 + + lazy var unlockVip3View: UIView = { + let view = UIView() + view.backgroundColor = .clear + view.isHidden = true + + let bgImg = UIImageView(image: UIImage(named: "PopupWindow/vip3_bg")) + bgImg.contentMode = .scaleAspectFill + view.addSubview(bgImg) + bgImg.layoutChain + .top(45) + .edgesHorzontal() + .bottom() + + view.addSubview(unlockVip3LottieView) + unlockVip3LottieView.layoutChain + .top(-120) + .right(-110) + .width(420) + .height(370) + + view.addSubview(unlockVip3CloseBtn) + unlockVip3CloseBtn.layoutChain + .bottom(kSafeBottomMargin + 15) + .left(30) + .height(50) + + view.addSubview(unlockVip3Btn) + unlockVip3Btn.layoutChain + .bottomToView(unlockVip3CloseBtn) + .right(30) + .height(50) + .widthToView(unlockVip3CloseBtn) + + unlockVip3CloseBtn.layoutChain.rightToLeftOfView(unlockVip3Btn, offset: -10) + + return view + }() + + lazy var unlockVip3LottieView: LottieAnimationView = { + let view = LottieAnimationView(name: "dialog_buy_vip_3") + view.loopMode = .loop + return view + }() + + lazy var unlockVip3CloseBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setTitle("稍后再说", for: .normal) + btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal) + btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) + btn.backgroundColor = .white + btn.cornerRadius = 25 + btn.borderWidth = 0.5 + btn.borderColor = UIColor(hexStr: "#16B3FF") + return btn + }() + + lazy var unlockVip3Btn: 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 + }() + + private func stopUnlockVip3Animations() { + unlockVip3LottieView.stop() + } + + private func slideInUnlockVip3Popup() { + stopUnlockVip3Animations() + bgView.isUserInteractionEnabled = false + unlockVip3View.isHidden = false + unlockVip3View.layoutIfNeeded() + unlockVip3View.transform = CGAffineTransform(translationX: 0, y: unlockVip3PopupHeight) + UIView.animate(withDuration: 0.45, delay: 0, + usingSpringWithDamping: 0.8, + initialSpringVelocity: 0.5, + options: .curveEaseOut) { + self.unlockVip3View.transform = .identity + } completion: { _ in + self.bgView.isUserInteractionEnabled = true + self.unlockVip3LottieView.play() + } + } + + private func slideOutUnlockVip3Popup() { + stopUnlockVip3Animations() + bgView.isUserInteractionEnabled = false + UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) { + self.unlockVip3View.transform = CGAffineTransform(translationX: 0, y: self.unlockVip3PopupHeight) + } completion: { _ in + Self.dismiss() + } + } + + // MARK: - 解锁会员 4 + private let unlockVip4PopupHeight: CGFloat = 368 + + lazy var unlockVip4View: UIView = { + let view = UIView() + view.backgroundColor = .clear + view.isHidden = true + + let bgImg = UIImageView(image: UIImage(named: "PopupWindow/vip4_bg")) + bgImg.contentMode = .scaleAspectFill + view.addSubview(bgImg) + bgImg.layoutChain + .top() + .edgesHorzontal() + .bottom() + + view.addSubview(unlockVip4AnimateView) + unlockVip4AnimateView.layoutChain + .top(143) + .rightToCenterXOfView(view, offset: 50) + .width(60) + .height(52) + + let label = UILabel() + label.text = "终身会员" + label.font = .systemFont(ofSize: 14, weight: .medium) + view.addSubview(label) + label.layoutChain + .leftToRightOfView(unlockVip4AnimateView, offset: 17) + .bottomToView(unlockVip4AnimateView) + + view.addSubview(unlockVip4DiscountLab) + unlockVip4DiscountLab.layoutChain + .bottomToView(label, offset: 10) + .leftToRightOfView(label) + + let unitLab = UILabel() + unitLab.text = "折" + unitLab.font = .systemFont(ofSize: 14, weight: .medium) + view.addSubview(unitLab) + unitLab.layoutChain + .bottomToView(label, offset: 0) + .leftToRightOfView(unlockVip4DiscountLab) + + view.addSubview(unlockVip4CloseBtn) + unlockVip4CloseBtn.layoutChain + .top() + .right(18) + .width(22) + .height(22) + + view.addSubview(unlockVip4Btn) + unlockVip4Btn.layoutChain + .centerX() + .width(224) + .height(50) + .bottom(49) + + return view + }() + + lazy var unlockVip4AnimateView: UIImageView = { + let view = UIImageView() + view.image = UIImage(named: "PopupWindow/vip4_animate") + return view + }() + + lazy var unlockVip4DiscountLab: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 44, weight: .bold) + label.textColor = UIColor(hexStr: "#FF4E41") + return label + }() + + lazy var unlockVip4CloseBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setImage(UIImage(named: "PopupWindow/close"), for: .normal) + btn.extendEdgeInsets = UIEdgeInsets(top: 30, left: 30, bottom: 30, right: 18) + return btn + }() + + lazy var unlockVip4Btn: 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 + }() + + private func startUnlockVip4Animations() { + stopUnlockVip4Animations() + + let animateScale = CABasicAnimation(keyPath: "transform.scale") + animateScale.fromValue = 1.0 + animateScale.toValue = 1.15 + animateScale.duration = 0.6 + animateScale.autoreverses = true + animateScale.repeatCount = .infinity + animateScale.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + unlockVip4AnimateView.layer.add(animateScale, forKey: "unlockVip4AnimateScale") + + let btnScale = CABasicAnimation(keyPath: "transform.scale") + btnScale.fromValue = 1.0 + btnScale.toValue = 1.08 + btnScale.duration = 0.8 + btnScale.autoreverses = true + btnScale.repeatCount = .infinity + btnScale.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + unlockVip4Btn.layer.add(btnScale, forKey: "unlockVip4BtnScale") + } + + private func stopUnlockVip4Animations() { + unlockVip4AnimateView.layer.removeAnimation(forKey: "unlockVip4AnimateScale") + unlockVip4Btn.layer.removeAnimation(forKey: "unlockVip4BtnScale") + } + + private func slideInUnlockVip4Popup() { + if let systemConfig = AppContextManager.shared.systemConfig, + let timeConfig = systemConfig.timeConfig { + unlockVip4DiscountLab.text = (String(format: "%.1f", timeConfig.min_discount/10)) + } + stopUnlockVip4Animations() + bgView.isUserInteractionEnabled = false + unlockVip4View.isHidden = false + unlockVip4View.layoutIfNeeded() + unlockVip4View.transform = CGAffineTransform(translationX: 0, y: unlockVip4PopupHeight) + UIView.animate(withDuration: 0.45, delay: 0, + usingSpringWithDamping: 0.8, + initialSpringVelocity: 0.5, + options: .curveEaseOut) { + self.unlockVip4View.transform = .identity + } completion: { _ in + self.bgView.isUserInteractionEnabled = true + self.startUnlockVip4Animations() + } + } + + private func slideOutUnlockVip4Popup() { + stopUnlockVip4Animations() + bgView.isUserInteractionEnabled = false + UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) { + self.unlockVip4View.transform = CGAffineTransform(translationX: 0, y: self.unlockVip4PopupHeight) + } completion: { _ in + Self.dismiss() + } + } + // MARK: - 注册账号 1 lazy var registerPopupView: UIView = { let view = UIView() @@ -423,7 +973,7 @@ class PopupWindow: UIView { .width(272) .height(50) .centerX() - + view.addSubview(register1UpLottieView) register1UpLottieView.layoutChain .bottomToTopOfView(logoImg, offset: 120) @@ -789,6 +1339,9 @@ class PopupWindow: UIView { addSubview(bgView) bgView.addSubview(searchPopupView) bgView.addSubview(unlockVip1View) + bgView.addSubview(unlockVip2View) + bgView.addSubview(unlockVip3View) + bgView.addSubview(unlockVip4View) bgView.addSubview(registerPopupView) bgView.addSubview(register2PopupView) @@ -811,6 +1364,22 @@ class PopupWindow: UIView { .bottom() .height(unlockVip1PopupHeight) + unlockVip2View.layoutChain + .edgesHorzontal() + .bottom() + .height(unlockVip2PopupHeight) + + unlockVip3View.layoutChain + .edgesHorzontal() + .bottom() + .height(unlockVip3PopupHeight) + + unlockVip4View.layoutChain + .edgesHorzontal() + .bottom() + .height(unlockVip4PopupHeight) + + setupRx() } diff --git a/QuickLocation/Section/PopupWindow/PromotionalActivitiesVC.swift b/QuickLocation/Section/PopupWindow/PromotionalActivitiesVC.swift new file mode 100644 index 0000000..293a09b --- /dev/null +++ b/QuickLocation/Section/PopupWindow/PromotionalActivitiesVC.swift @@ -0,0 +1,45 @@ +// +// PromotionalActivitiesVC.swift +// QuickLocation +// +// Created by 八条 on 2026/7/3. +// + +import UIKit +import RxSwift +import RxCocoa +import RxDataSources + +class PromotionalActivitiesVC: BaseViewController { + + fileprivate var rootView: PromotionalActivitiesView! + var onDismiss: (() -> Void)? + + override func loadView() { + rootView = PromotionalActivitiesView(frame: UIScreen.main.bounds) + view = rootView + } + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + requestRechargeInfo() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + if isMovingFromParent { + onDismiss?() + } + } + + // MARK: - API + private func requestRechargeInfo() { + DLToast.showLoading() + OrderService.rechargeInfo(type: "member").subscribe(onNext: { [weak self] response in + guard let self = self else { return } + self.rootView.startCountdown(endTime: 1769875199) + }).disposed(by: disposeBag) + } +} diff --git a/QuickLocation/Section/PopupWindow/PromotionalActivitiesView.swift b/QuickLocation/Section/PopupWindow/PromotionalActivitiesView.swift new file mode 100644 index 0000000..127cbfd --- /dev/null +++ b/QuickLocation/Section/PopupWindow/PromotionalActivitiesView.swift @@ -0,0 +1,321 @@ +// +// PromotionalActivitiesView.swift +// QuickLocation +// +// Created by 八条 on 2026/7/3. +// + +import UIKit +import RxSwift +import RxCocoa + +class PromotionalActivitiesView: UIView { + + var disposeBag = DisposeBag() + + private func setupRx() { + backBtn.rx.tap.subscribe(onNext: { _ in + VipWaivePopView.show() + }).disposed(by: disposeBag) + + } + + private func setupUI() { + addSubview(scrollView) + + addSubview(navBarView) + navBarView.addSubview(navTitleLabel) + addSubview(backBtn) + + navBarView.layoutChain + .edges(excludingEdge: .bottom) + .height(kNaviHeight) + + navTitleLabel.layoutChain + .top(kStatusBarHeight + 12) + .centerY(backBtn) + .centerX() + + backBtn.layoutChain + .top(kStatusBarHeight + 12) + .left(15) + .width(24) + .height(24) + + addSubview(bottomView) + bottomView.layoutChain + .edges(excludingEdge: .top) + .height(120) + + scrollView.layoutChain + .edges(excludingEdge: .bottom) + .bottomToTopOfView(bottomView) + } + + lazy var navBarView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.alpha = 0 + 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: 15, right: 100) + return btn + }() + + lazy var scrollView: UIScrollView = { + let view = UIScrollView() + view.backgroundColor = UIColor(hexStr: "#F6F9FB") + view.showsVerticalScrollIndicator = false + view.delegate = self + view.bounces = false + view.contentInsetAdjustmentBehavior = .never + + view.addSubview(scrollContentView) + scrollContentView.layoutChain + .edges().widthToView(view) + + let headerBgImgView = UIImageView(image: UIImage(named: "PromotionalActivities/header_bg")) + headerBgImgView.contentMode = .scaleAspectFill + + scrollContentView.addSubview(headerBgImgView) + headerBgImgView.layoutChain + .edges(excludingEdge: .bottom) + .heightToWidth(285/375) + + let infoView = UIView() + infoView.backgroundColor = UIColor(hexStr: "#F6F9FB") + infoView.layer.cornerRadius = 30 + + scrollContentView.addSubview(infoView) + infoView.layoutChain + .topToBottomOfView(headerBgImgView, offset: -65) + .edges(excludingEdge: .top) + + let iconView = UIImageView(image: UIImage(named: "PromotionalActivities/vip_icon")) + infoView.addSubview(iconView) + iconView.layoutChain + .top(20) + .left(15) + .width(20) + .heightToWidth(1) + + let titleLab = UILabel() + titleLab.text = "即刻加入会员" + titleLab.font = .systemFont(ofSize: 16, weight: .semibold) + titleLab.textColor = UIColor(hexStr: "#333333") + infoView.addSubview(titleLab) + titleLab.layoutChain + .leftToRightOfView(iconView, offset: 4) + .centerY(iconView) + + let agreementView = UIView() + infoView.addSubview(agreementView) + agreementView.layoutChain + .topToBottomOfView(titleLab, offset: 18) + .left(15) + .right(15) + .height(30) + + agreementView.addSubview(agreementCheckBtn) + agreementCheckBtn.layoutChain + .left(0) + .centerY() + .width(16) + .height(16) + + agreementView.addSubview(agreementTextView) + agreementTextView.layoutChain + .leftToRightOfView(agreementCheckBtn, offset: 6) + .right(0) + .centerY() + .bottom(0) + + let benefitsImgView = UIImageView(image: UIImage(named: "PromotionalActivities/benefits")) + benefitsImgView.contentMode = .scaleAspectFill + infoView.addSubview(benefitsImgView) + benefitsImgView.layoutChain + .topToBottomOfView(agreementView, offset: 20) + .edgesHorzontal(15) + .bottom(15, relation: .greaterThanOrEqual) + .heightToWidth(148/375) + + return view + }() + + lazy var scrollContentView: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + + lazy var bottomView: UIView = { + let view = UIView() + view.backgroundColor = .white + + view.addSubview(countdownLabel) + countdownLabel.layoutChain + .top(11) + .centerX() + + view.addSubview(confirmBtn) + confirmBtn.layoutChain + .edgesHorzontal(30) + .bottom(35) + .height(50) + + return view + }() + + lazy var agreementCheckBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setImage(UIImage(named: "Common/checkbox"), for: .normal) + btn.setImage(UIImage(named: "Common/checkbox_on"), for: .selected) + btn.rx.tap.subscribe(onNext: { [weak btn] in + btn?.isSelected.toggle() + }).disposed(by: disposeBag) + return btn + }() + + lazy var agreementTextView: UITextView = { + let tv = UITextView() + tv.isEditable = false + tv.isScrollEnabled = false + tv.backgroundColor = .clear + tv.textContainerInset = .zero + tv.textContainer.lineFragmentPadding = 0 + tv.delegate = self + let text = "购买前请先阅读隐私政策和服务条款" + let attr = NSMutableAttributedString(string: text, attributes: [ + .font: UIFont.systemFont(ofSize: 12), + .foregroundColor: UIColor(hexStr: "#999999") + ]) + let privacyRange = (text as NSString).range(of: "隐私政策") + let termsRange = (text as NSString).range(of: "服务条款") + let linkColor = UIColor(hexStr: "#16B3FF") + attr.addAttributes([.foregroundColor: linkColor, .link: URLManager.shared.privacyPolicyUrl], range: privacyRange) + attr.addAttributes([.foregroundColor: linkColor, .link: URLManager.shared.userAgreementUrl], range: termsRange) + tv.attributedText = attr + tv.linkTextAttributes = [.foregroundColor: linkColor] + return tv + }() + + lazy var countdownLabel: UILabel = { + let label = UILabel() + label.textAlignment = .center + return label + }() + + lazy var confirmBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setTitle("立即开通", for: .normal) + btn.setTitleColor(.white, for: .normal) + btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) + btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal) + btn.cornerRadius = 25 + return btn + }() + + private var countdownEndTime: TimeInterval = 0 + private var countdownTimer: Timer? + + func startCountdown(endTime: TimeInterval) { + countdownEndTime = endTime + updateCountdown() + countdownTimer?.invalidate() + countdownTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in + self?.updateCountdown() + } + } + + private func stopCountdown() { + countdownTimer?.invalidate() + countdownTimer = nil + } + + private func updateCountdown() { + let remaining = max(0, countdownEndTime - Date().timeIntervalSince1970) + let totalSeconds = Int(remaining) + let days = totalSeconds / 86400 + let hours = (totalSeconds % 86400) / 3600 + let minutes = (totalSeconds % 3600) / 60 + let seconds = totalSeconds % 60 + + let attr = NSMutableAttributedString() + + let normalStyle: [NSAttributedString.Key: Any] = [ + .foregroundColor: UIColor(hexStr: "#FD501B"), + .font: UIFont.systemFont(ofSize: 12) + ] + let timeStyle: [NSAttributedString.Key: Any] = [ + .foregroundColor: UIColor.white, + .font: UIFont.systemFont(ofSize: 12), + .backgroundColor: UIColor(hexStr: "#FD501B") + ] + + attr.append(NSAttributedString(string: "优惠活动倒计时 ", attributes: normalStyle)) + + let timeStr: String + if days > 0 { + timeStr = String(format: "%d天%02d:%02d:%02d", days, hours, minutes, seconds) + } else { + timeStr = String(format: "%02d:%02d:%02d", hours, minutes, seconds) + } + attr.append(NSAttributedString(string: timeStr, attributes: timeStyle)) + + attr.append(NSAttributedString(string: " 限时特惠", attributes: normalStyle)) + + countdownLabel.attributedText = attr + } + + override func layoutSubviews() { + super.layoutSubviews() + } + + override func willMove(toSuperview newSuperview: UIView?) { + super.willMove(toSuperview: newSuperview) + if newSuperview == nil { + stopCountdown() + } + } + + override init(frame: CGRect) { + super.init(frame: .zero) + backgroundColor = .white + setupUI() + setupRx() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - UIScrollViewDelegate +extension PromotionalActivitiesView: UIScrollViewDelegate { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + let maxY = scrollView.contentOffset.y + let alpha = maxY / kNaviHeight + navBarView.alpha = alpha < 0.0 ? 0.0 : alpha + } +} + +// MARK: - UITextViewDelegate +extension PromotionalActivitiesView: UITextViewDelegate { + func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { + AppRouter.push(Route.web, userInfo: ["url": URL.absoluteString]) + return false + } +} diff --git a/QuickLocation/Section/VipRecharge/VipRechargeView.swift b/QuickLocation/Section/VipRecharge/VipRechargeView.swift index 665ad95..5269344 100644 --- a/QuickLocation/Section/VipRecharge/VipRechargeView.swift +++ b/QuickLocation/Section/VipRecharge/VipRechargeView.swift @@ -95,9 +95,7 @@ class VipRechargeView: UIView { .widthToView(scrollView) headerBgImgView.layoutChain - .top(-kStatusBarHeight) - .edgesHorzontal() - // .edges(excludingEdge: .bottom) + .edges(excludingEdge: .bottom) .heightToWidth(267/375) vipRightsDetailView.layoutChain @@ -174,6 +172,7 @@ class VipRechargeView: UIView { view.showsVerticalScrollIndicator = false view.delegate = self view.bounces = false + view.contentInsetAdjustmentBehavior = .never return view }()