|
|
@ -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 = "<group>"; };
|
||||
30A87A702FEF7BED0095E7C6 /* SearchLocationResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationResultView.swift; sourceTree = "<group>"; };
|
||||
30ACD54B2FF6336000174861 /* PopupWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupWindow.swift; sourceTree = "<group>"; };
|
||||
30ACD54D2FF7942E00174861 /* PromotionalActivitiesVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromotionalActivitiesVC.swift; sourceTree = "<group>"; };
|
||||
30ACD54F2FF7943A00174861 /* PromotionalActivitiesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromotionalActivitiesView.swift; sourceTree = "<group>"; };
|
||||
30ACD67A2FF7B36A00174861 /* PopupQueueManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupQueueManager.swift; sourceTree = "<group>"; };
|
||||
30B74B392FF2115A00F6744D /* GroupScheduleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupScheduleView.swift; sourceTree = "<group>"; };
|
||||
30B74B3B2FF2117900F6744D /* GroupScheduleVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupScheduleVC.swift; sourceTree = "<group>"; };
|
||||
30B74B402FF2437E00F6744D /* GroupMemberListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberListVC.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -1344,7 +1350,10 @@
|
|||
30ACD54A2FF6332200174861 /* PopupWindow */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30ACD67A2FF7B36A00174861 /* PopupQueueManager.swift */,
|
||||
30ACD54B2FF6336000174861 /* PopupWindow.swift */,
|
||||
30ACD54D2FF7942E00174861 /* PromotionalActivitiesVC.swift */,
|
||||
30ACD54F2FF7943A00174861 /* PromotionalActivitiesView.swift */,
|
||||
);
|
||||
path = PopupWindow;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -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 */,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip2_bg.imageset/Group_2092@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip2_bg.imageset/Group_2092@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 553 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip2_icon.imageset/Mask_group@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip2_icon.imageset/Mask_group@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 23 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip2_logo.imageset/Group 2091@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip2_logo.imageset/Group 2091@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 210 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip2_text.imageset/vip2_text@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip2_text.imageset/vip2_text@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 38 KiB |
22
QuickLocation/Assets.xcassets/PopupWindow/vip2_text_bg.imageset/Contents.json
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip2_text_bg.imageset/Group_2084@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip2_text_bg.imageset/Group_2084@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip3_bg.imageset/Group_2094@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip3_bg.imageset/Group_2094@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 273 KiB |
22
QuickLocation/Assets.xcassets/PopupWindow/vip4_animate.imageset/Contents.json
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip4_animate.imageset/Group 2109@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
QuickLocation/Assets.xcassets/PopupWindow/vip4_animate.imageset/Group 2109@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 398 KiB |
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
22
QuickLocation/Assets.xcassets/PromotionalActivities/benefits.imageset/Contents.json
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
BIN
QuickLocation/Assets.xcassets/PromotionalActivities/benefits.imageset/benefits@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
QuickLocation/Assets.xcassets/PromotionalActivities/benefits.imageset/benefits@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 70 KiB |
22
QuickLocation/Assets.xcassets/PromotionalActivities/header_bg.imageset/Contents.json
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
BIN
QuickLocation/Assets.xcassets/PromotionalActivities/header_bg.imageset/banner@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
QuickLocation/Assets.xcassets/PromotionalActivities/header_bg.imageset/banner@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 396 KiB |
22
QuickLocation/Assets.xcassets/PromotionalActivities/vip_icon.imageset/Contents.json
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
BIN
QuickLocation/Assets.xcassets/PromotionalActivities/vip_icon.imageset/Group_2426@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
QuickLocation/Assets.xcassets/PromotionalActivities/vip_icon.imageset/Group_2426@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
|
|
@ -43,6 +43,7 @@
|
|||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>douyu.otf</string>
|
||||
<string>zihunbiantaoti-r.ttf</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<GroupMemberListSectionModel> = {
|
||||
return RxTableViewSectionedReloadDataSource<GroupMemberListSectionModel>(
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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: "隐私与协议"),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}()
|
||||
|
||||
|
|
|
|||