diff --git a/QuickLocation.xcodeproj/project.pbxproj b/QuickLocation.xcodeproj/project.pbxproj index 4ee2652..03cb98d 100644 --- a/QuickLocation.xcodeproj/project.pbxproj +++ b/QuickLocation.xcodeproj/project.pbxproj @@ -225,6 +225,8 @@ 30C4C01D2FDBF557009215C1 /* RemoveMemberViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C01C2FDBF557009215C1 /* RemoveMemberViewModel.swift */; }; 30C4C0202FDC0EC5009215C1 /* GroupInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C01F2FDC0EC5009215C1 /* GroupInfoView.swift */; }; 30C4C0222FDC0ED3009215C1 /* GroupInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C0212FDC0ED3009215C1 /* GroupInfoVC.swift */; }; + 30C6666C2FFB7C4000E62B25 /* IAPManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C6666B2FFB7C4000E62B25 /* IAPManager.swift */; }; + 30C6679E2FFB7FEF00E62B25 /* SharePopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C6679C2FFB7FEF00E62B25 /* SharePopView.swift */; }; 30CCDE4E2FE26CEA00F5214A /* PayResultPopVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCDE4D2FE26CEA00F5214A /* PayResultPopVC.swift */; }; 30CCDE512FE2785D00F5214A /* SignInVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCDE502FE2785D00F5214A /* SignInVC.swift */; }; 30CCDE532FE2786600F5214A /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCDE522FE2786600F5214A /* SignInView.swift */; }; @@ -524,6 +526,8 @@ 30C4C01C2FDBF557009215C1 /* RemoveMemberViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveMemberViewModel.swift; sourceTree = ""; }; 30C4C01F2FDC0EC5009215C1 /* GroupInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInfoView.swift; sourceTree = ""; }; 30C4C0212FDC0ED3009215C1 /* GroupInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInfoVC.swift; sourceTree = ""; }; + 30C6666B2FFB7C4000E62B25 /* IAPManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAPManager.swift; sourceTree = ""; }; + 30C6679C2FFB7FEF00E62B25 /* SharePopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharePopView.swift; sourceTree = ""; }; 30CCDE4D2FE26CEA00F5214A /* PayResultPopVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayResultPopVC.swift; sourceTree = ""; }; 30CCDE502FE2785D00F5214A /* SignInVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInVC.swift; sourceTree = ""; }; 30CCDE522FE2786600F5214A /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = ""; }; @@ -910,6 +914,7 @@ 305A752A2FCA8C7000227D26 /* Theme */, 305A752C2FCA8C7000227D26 /* URL */, 30D87CD52FDF9F1900E958FD /* MQTT */, + 30C6666A2FFB7C3000E62B25 /* IAPManager */, ); path = Manager; sourceTree = ""; @@ -1072,6 +1077,7 @@ 30DC18592FD11E7A0041DCD1 /* Web */, 30ACD54A2FF6332200174861 /* PopupWindow */, 30EFF3AD2FD7FF1400EB35D4 /* TextInput */, + 30C6679D2FFB7FEF00E62B25 /* Share */, ); path = Section; sourceTree = ""; @@ -1436,6 +1442,22 @@ path = GroupInfo; sourceTree = ""; }; + 30C6666A2FFB7C3000E62B25 /* IAPManager */ = { + isa = PBXGroup; + children = ( + 30C6666B2FFB7C4000E62B25 /* IAPManager.swift */, + ); + path = IAPManager; + sourceTree = ""; + }; + 30C6679D2FFB7FEF00E62B25 /* Share */ = { + isa = PBXGroup; + children = ( + 30C6679C2FFB7FEF00E62B25 /* SharePopView.swift */, + ); + path = Share; + sourceTree = ""; + }; 30CCDE4F2FE2782700F5214A /* SignIn */ = { isa = PBXGroup; children = ( @@ -1840,6 +1862,7 @@ 305A769C2FCA8C7000227D26 /* PopupViewController.swift in Sources */, 305A769D2FCA8C7000227D26 /* PopupViewController+Extension.swift in Sources */, 30EFF3C42FDA431D00EB35D4 /* ChangePhoneVC.swift in Sources */, + 30C6666C2FFB7C4000E62B25 /* IAPManager.swift in Sources */, 305A769E2FCA8C7000227D26 /* TextArrowCell.swift in Sources */, 305A769F2FCA8C7000227D26 /* TextContentArrowCell.swift in Sources */, 30D87D042FE1336300E958FD /* NavigationVC.swift in Sources */, @@ -1925,6 +1948,7 @@ 305A76CA2FCA8C7000227D26 /* AppSettings.swift in Sources */, 305A76CB2FCA8C7000227D26 /* Authorize.swift in Sources */, 30CCDE552FE2903100F5214A /* SignInModel.swift in Sources */, + 30C6679E2FFB7FEF00E62B25 /* SharePopView.swift in Sources */, 305A76CC2FCA8C7000227D26 /* FileTools.swift in Sources */, 305A76CD2FCA8C7000227D26 /* Permission.swift in Sources */, 30D74AB02FEA13ED0050EB2C /* ScheduleVC.swift in Sources */, diff --git a/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate b/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate index 72a841c..338acd6 100644 Binary files a/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate and b/QuickLocation.xcworkspace/xcuserdata/yanghong.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/QuickLocation/API/OrderAPI.swift b/QuickLocation/API/OrderAPI.swift index e413dcd..81435bf 100644 --- a/QuickLocation/API/OrderAPI.swift +++ b/QuickLocation/API/OrderAPI.swift @@ -15,7 +15,7 @@ enum OrderAPI { /// - Parameters: /// - type: 类型 member case rechargeInfo(type: String) - + /// 支付参数 /// - Parameters: /// - goodsId: 商品ID @@ -23,19 +23,24 @@ enum OrderAPI { /// - source: center /// - extra: 额外参数 case orderPayParams(goodsId: String, payType: String, source: String, extra: [String:Any]=[:]) + + /// Apple 内购通知 + case appleNotify(receipt: String, productId: String, transactionId: String) } extension OrderAPI: MultiTargetProtocol { - + var path: String { switch self { case .rechargeInfo: return "api/order/goods" case .orderPayParams: return "api/order" + case .appleNotify: + return "api/order/notify/apple" } } - + var method: Moya.Method { switch self { case .rechargeInfo: @@ -44,14 +49,14 @@ extension OrderAPI: MultiTargetProtocol { return .post } } - + var task: Moya.Task { switch self { case let .rechargeInfo(type): var params = Parameters() params["type"] = type return .requestParameters(parameters: params, encoding: URLEncoding()) - + case let .orderPayParams(goodsId, payType, source, extra): var params = Parameters() params["goods_id"] = goodsId @@ -60,6 +65,13 @@ extension OrderAPI: MultiTargetProtocol { params["pay_source"] = "app" params["extra"] = extra return .requestParameters(parameters: params, encoding: JSONEncoding()) + + case let .appleNotify(receipt, productId, transactionId): + var params = Parameters() + params["receipt_data"] = receipt + params["product_id"] = productId + params["transaction_id"] = transactionId + return .requestParameters(parameters: params, encoding: JSONEncoding()) } } } diff --git a/QuickLocation/AppDelegate.swift b/QuickLocation/AppDelegate.swift index 8b991e8..635dc6e 100644 --- a/QuickLocation/AppDelegate.swift +++ b/QuickLocation/AppDelegate.swift @@ -18,25 +18,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { - + setupLocation() ApiManager.shared.setup() AppRouter.shared.setup() - + return true } - + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - + + IAPManager.shared.startObserving() + IQKeyboardManager.shared.isEnabled = true IQKeyboardManager.shared.enableAutoToolbar = false IQKeyboardManager.shared.resignOnTouchOutside = true // 微信 WXApi.registerApp(AppSettings.kAppsWXApiAppId, universalLink: AppSettings.kAppsUniversalLink) - - + + // 高德地图 #if !targetEnvironment(simulator) AMapServices.shared().apiKey = AppSettings.kAppsAMapAppId @@ -53,19 +55,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate { print("preGetToken: \(preDic)") } #endif - + // OpenIM GroupIMService.shared.initSDK() - + window = UIWindow(frame: UIScreen.main.bounds) window?.backgroundColor = .white window?.overrideUserInterfaceStyle = .light window?.rootViewController = LaunchViewController() window?.makeKeyAndVisible() - + return true } - + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { print("📱 application open url: \(url.absoluteString) host: \(url.host ?? "")") // 处理支付宝支付结果(不限制 host,适应新版 SDK) @@ -75,17 +77,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { guard let result = resultDic, let resultStatus = result["resultStatus"] as? String, resultStatus != "6001" else { return } - + } return true } else if url.host == "pay" { return WXApi.handleOpen(url, delegate: self) } - + return true } - + func setupLocation() { let manager = AuthorizeManager.manager(type: .locationWhenInUse) if manager?.authorizeStatus() != .authorized { @@ -112,7 +114,7 @@ extension AppDelegate { static var keyWindow: UIWindow? { UIApplication.shared.windows.filter { $0.isKeyWindow }.first } - + static var rootViewController: UIViewController? { getRootViewController(keyWindow?.rootViewController) } @@ -141,7 +143,7 @@ extension AppDelegate: WXApiDelegate { func onReq(_ req: BaseReq) { print(req) } - + func onResp(_ resp: BaseResp) { if resp.isKind(of: PayResp.self), let response: PayResp = resp as? PayResp { @@ -150,7 +152,7 @@ extension AppDelegate: WXApiDelegate { if response.errCode == 0 { // 支付成功 DispatchQueue.global().async { DispatchQueue.main.async { - + } } } diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/normal_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/PromotionalActivities/normal_bg.imageset/Contents.json new file mode 100644 index 0000000..19a1c34 --- /dev/null +++ b/QuickLocation/Assets.xcassets/PromotionalActivities/normal_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_2432@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_2432@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/normal_bg.imageset/Group_2432@2x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/normal_bg.imageset/Group_2432@2x.png new file mode 100644 index 0000000..b10806d Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/normal_bg.imageset/Group_2432@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/normal_bg.imageset/Group_2432@3x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/normal_bg.imageset/Group_2432@3x.png new file mode 100644 index 0000000..4321c1c Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/normal_bg.imageset/Group_2432@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/normal_on_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/PromotionalActivities/normal_on_bg.imageset/Contents.json new file mode 100644 index 0000000..20dfc44 --- /dev/null +++ b/QuickLocation/Assets.xcassets/PromotionalActivities/normal_on_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "normal_on_bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "normal_on_bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/normal_on_bg.imageset/normal_on_bg@2x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/normal_on_bg.imageset/normal_on_bg@2x.png new file mode 100644 index 0000000..e889cda Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/normal_on_bg.imageset/normal_on_bg@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/normal_on_bg.imageset/normal_on_bg@3x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/normal_on_bg.imageset/normal_on_bg@3x.png new file mode 100644 index 0000000..edfc917 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/normal_on_bg.imageset/normal_on_bg@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_bg.imageset/Contents.json new file mode 100644 index 0000000..d3af90b --- /dev/null +++ b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "recommend_bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "recommend_bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_bg.imageset/recommend_bg@2x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_bg.imageset/recommend_bg@2x.png new file mode 100644 index 0000000..bb35f17 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_bg.imageset/recommend_bg@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_bg.imageset/recommend_bg@3x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_bg.imageset/recommend_bg@3x.png new file mode 100644 index 0000000..c42b15c Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_bg.imageset/recommend_bg@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_icon.imageset/Contents.json b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_icon.imageset/Contents.json new file mode 100644 index 0000000..0df860c --- /dev/null +++ b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_2429@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_2429@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_icon.imageset/Group_2429@2x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_icon.imageset/Group_2429@2x.png new file mode 100644 index 0000000..1956b02 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_icon.imageset/Group_2429@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_icon.imageset/Group_2429@3x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_icon.imageset/Group_2429@3x.png new file mode 100644 index 0000000..261e614 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_icon.imageset/Group_2429@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_on_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_on_bg.imageset/Contents.json new file mode 100644 index 0000000..5bc6c4f --- /dev/null +++ b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_on_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "recommend_on_bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "recommend_on_bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_on_bg.imageset/recommend_on_bg@2x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_on_bg.imageset/recommend_on_bg@2x.png new file mode 100644 index 0000000..cad28e9 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_on_bg.imageset/recommend_on_bg@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_on_bg.imageset/recommend_on_bg@3x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_on_bg.imageset/recommend_on_bg@3x.png new file mode 100644 index 0000000..15a60a3 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/recommend_on_bg.imageset/recommend_on_bg@3x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/tips_bg.imageset/Contents.json b/QuickLocation/Assets.xcassets/PromotionalActivities/tips_bg.imageset/Contents.json new file mode 100644 index 0000000..e5cef62 --- /dev/null +++ b/QuickLocation/Assets.xcassets/PromotionalActivities/tips_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_2428@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_2428@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/tips_bg.imageset/Group_2428@2x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/tips_bg.imageset/Group_2428@2x.png new file mode 100644 index 0000000..41bf837 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/tips_bg.imageset/Group_2428@2x.png differ diff --git a/QuickLocation/Assets.xcassets/PromotionalActivities/tips_bg.imageset/Group_2428@3x.png b/QuickLocation/Assets.xcassets/PromotionalActivities/tips_bg.imageset/Group_2428@3x.png new file mode 100644 index 0000000..23863f8 Binary files /dev/null and b/QuickLocation/Assets.xcassets/PromotionalActivities/tips_bg.imageset/Group_2428@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Share/Contents.json b/QuickLocation/Assets.xcassets/Share/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Share/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/QuickLocation/Assets.xcassets/Share/friend.imageset/Contents.json b/QuickLocation/Assets.xcassets/Share/friend.imageset/Contents.json new file mode 100644 index 0000000..413361f --- /dev/null +++ b/QuickLocation/Assets.xcassets/Share/friend.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "friend@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "friend@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Share/friend.imageset/friend@2x.png b/QuickLocation/Assets.xcassets/Share/friend.imageset/friend@2x.png new file mode 100644 index 0000000..a53cd38 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Share/friend.imageset/friend@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Share/friend.imageset/friend@3x.png b/QuickLocation/Assets.xcassets/Share/friend.imageset/friend@3x.png new file mode 100644 index 0000000..3ffa3ca Binary files /dev/null and b/QuickLocation/Assets.xcassets/Share/friend.imageset/friend@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Share/moments.imageset/Contents.json b/QuickLocation/Assets.xcassets/Share/moments.imageset/Contents.json new file mode 100644 index 0000000..6ecab9a --- /dev/null +++ b/QuickLocation/Assets.xcassets/Share/moments.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "moments@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "moments@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Share/moments.imageset/moments@2x.png b/QuickLocation/Assets.xcassets/Share/moments.imageset/moments@2x.png new file mode 100644 index 0000000..ed7bc01 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Share/moments.imageset/moments@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Share/moments.imageset/moments@3x.png b/QuickLocation/Assets.xcassets/Share/moments.imageset/moments@3x.png new file mode 100644 index 0000000..f69d0cb Binary files /dev/null and b/QuickLocation/Assets.xcassets/Share/moments.imageset/moments@3x.png differ diff --git a/QuickLocation/Assets.xcassets/Share/url.imageset/Contents.json b/QuickLocation/Assets.xcassets/Share/url.imageset/Contents.json new file mode 100644 index 0000000..99eb468 --- /dev/null +++ b/QuickLocation/Assets.xcassets/Share/url.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "url@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "url@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/Share/url.imageset/url@2x.png b/QuickLocation/Assets.xcassets/Share/url.imageset/url@2x.png new file mode 100644 index 0000000..0c2dae3 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Share/url.imageset/url@2x.png differ diff --git a/QuickLocation/Assets.xcassets/Share/url.imageset/url@3x.png b/QuickLocation/Assets.xcassets/Share/url.imageset/url@3x.png new file mode 100644 index 0000000..7a61b29 Binary files /dev/null and b/QuickLocation/Assets.xcassets/Share/url.imageset/url@3x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/apple.imageset/Contents.json b/QuickLocation/Assets.xcassets/VipRecharge/apple.imageset/Contents.json new file mode 100644 index 0000000..7c575d4 --- /dev/null +++ b/QuickLocation/Assets.xcassets/VipRecharge/apple.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "apple@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "apple@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/QuickLocation/Assets.xcassets/VipRecharge/apple.imageset/apple@2x.png b/QuickLocation/Assets.xcassets/VipRecharge/apple.imageset/apple@2x.png new file mode 100644 index 0000000..40f97b0 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/apple.imageset/apple@2x.png differ diff --git a/QuickLocation/Assets.xcassets/VipRecharge/apple.imageset/apple@3x.png b/QuickLocation/Assets.xcassets/VipRecharge/apple.imageset/apple@3x.png new file mode 100644 index 0000000..5104206 Binary files /dev/null and b/QuickLocation/Assets.xcassets/VipRecharge/apple.imageset/apple@3x.png differ diff --git a/QuickLocation/Manager/Account/UserConfigModel.swift b/QuickLocation/Manager/Account/UserConfigModel.swift index 656b26a..9934cde 100644 --- a/QuickLocation/Manager/Account/UserConfigModel.swift +++ b/QuickLocation/Manager/Account/UserConfigModel.swift @@ -116,6 +116,8 @@ struct PopupWindowTimeModel: Mappable { var pay_pop_time: Int = 0 /// vip折扣 var min_discount: Double = 0 + /// 默认支付方式 1 支付宝 2 微信 + var pay_pop_type: Int = 0 init?(map: Map) { @@ -127,5 +129,6 @@ struct PopupWindowTimeModel: Mappable { signup_tip <- map["signup_tip"] pay_pop_time <- map["pay_pop_time"] min_discount <- map["min_discount"] + pay_pop_type <- map["pay_pop_type"] } } diff --git a/QuickLocation/Manager/IAPManager/IAPManager.swift b/QuickLocation/Manager/IAPManager/IAPManager.swift new file mode 100644 index 0000000..f72fee1 --- /dev/null +++ b/QuickLocation/Manager/IAPManager/IAPManager.swift @@ -0,0 +1,81 @@ +// +// IAPManager.swift +// QuickLocation +// +// Created by 八条 on 2026/7/3. +// + +import StoreKit +import RxSwift + +class IAPManager: NSObject { + static let shared = IAPManager() + private let disposeBag = DisposeBag() + + private var currentGoodsId: String = "" + private var purchaseCompletion: ((Bool, String?) -> Void)? + + func startObserving() { + SKPaymentQueue.default().add(self) + } + + func purchase(goodsId: String, completion: @escaping (Bool, String?) -> Void) { + currentGoodsId = goodsId + purchaseCompletion = completion + let request = SKProductsRequest(productIdentifiers: [goodsId]) + request.delegate = self + request.start() + } +} + +// MARK: - SKProductsRequestDelegate +extension IAPManager: SKProductsRequestDelegate { + func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { + guard let product = response.products.first else { + purchaseCompletion?(false, "商品不可用") + return + } + SKPaymentQueue.default().add(SKPayment(product: product)) + } +} + +// MARK: - SKPaymentTransactionObserver +extension IAPManager: SKPaymentTransactionObserver { + func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { + for tx in transactions { + switch tx.transactionState { + case .purchased: + verifyReceipt(transaction: tx) + case .failed: + queue.finishTransaction(tx) + let msg = tx.error?.localizedDescription ?? "支付失败" + purchaseCompletion?(false, msg) + default: + break + } + } + } + + private func verifyReceipt(transaction: SKPaymentTransaction) { + guard let receiptURL = Bundle.main.appStoreReceiptURL, + let receiptData = try? Data(contentsOf: receiptURL) else { + SKPaymentQueue.default().finishTransaction(transaction) + purchaseCompletion?(false, "获取凭证失败") + return + } + let receipt = receiptData.base64EncodedString() + let txId = transaction.transactionIdentifier ?? "" + + OrderService.appleNotify( + receipt: receipt, + productId: currentGoodsId, + transactionId: txId + ).subscribe(onNext: { [weak self] _ in + SKPaymentQueue.default().finishTransaction(transaction) + self?.purchaseCompletion?(true, nil) + }, onError: { [weak self] error in + SKPaymentQueue.default().finishTransaction(transaction) + self?.purchaseCompletion?(false, error.localizedDescription) + }).disposed(by: disposeBag) + } +} diff --git a/QuickLocation/Section/Group/InviteJoin/InviteJoinVC.swift b/QuickLocation/Section/Group/InviteJoin/InviteJoinVC.swift index 3c4c119..9ecf88c 100644 --- a/QuickLocation/Section/Group/InviteJoin/InviteJoinVC.swift +++ b/QuickLocation/Section/Group/InviteJoin/InviteJoinVC.swift @@ -8,6 +8,7 @@ import UIKit import ObjectMapper import RxSwift +import RxCocoa class InviteJoinVC: BaseViewController { @@ -25,8 +26,13 @@ class InviteJoinVC: BaseViewController { // Do any additional setup after loading the view. - guard let model = groupInModel else { return } + // 分享 + rootView.shareAppBtn.rx.tap.subscribe(onNext: { _ in + let url = "https://smartdrive.zuom8.cn/jisu/share/user/" + SharePopView.show(shareURL: url) + }).disposed(by: disposeBag) + guard let model = groupInModel else { return } rootView.groupIcon.image = model.groupIcon rootView.groupNameLab.text = model.name requestOperateGroup(key: model.group_key) diff --git a/QuickLocation/Section/Home/HomeViewController.swift b/QuickLocation/Section/Home/HomeViewController.swift index 6708221..eab8424 100644 --- a/QuickLocation/Section/Home/HomeViewController.swift +++ b/QuickLocation/Section/Home/HomeViewController.swift @@ -83,7 +83,7 @@ class HomeViewController: BaseViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - + } // MARK: - MQTT 位置上报 @@ -246,6 +246,13 @@ class HomeViewController: BaseViewController { guard let self = self else { return } self.requestSendEmote(emoteIdx: emoteIdx) } + + // 分享 + rootView.interactionView.onShare = { [weak self] in + guard let self = self, let member = self.rootView.interactionView.currentMember else { return } + let url = "https://smartdrive.zuom8.cn/jisu/share/user/\(member.id)" + SharePopView.show(shareURL: url) + } } private func bindViewModel() { diff --git a/QuickLocation/Section/Home/InteractionView.swift b/QuickLocation/Section/Home/InteractionView.swift index ddc459d..92a927e 100644 --- a/QuickLocation/Section/Home/InteractionView.swift +++ b/QuickLocation/Section/Home/InteractionView.swift @@ -14,17 +14,17 @@ import Lottie class InteractionView: UIView { var disposeBag = DisposeBag() - + private static let emojiCols = 4 private static let emojiRows = 3 private static let emojiPerPage = emojiCols * emojiRows - + func configure(member: CircleMember) { self.currentMember = member avaterImgView.image = UIImage(named: "UserIcon/\(member.avatar)") nameLab.text = member.name locationLab.text = member.address - + // 会员权益 if AppContextManager.shared.vip > 1 { batteryInfoView.isHidden = member.battery.int == 0 @@ -33,7 +33,7 @@ class InteractionView: UIView { let batteryPercent = min(CGFloat(batteryInt), 100) batteryView.layoutChain.width(CGFloat(16 - 1) * batteryPercent / 100.0) batteryLab.text = "\(member.battery)%" - + // 趣味表情 lockView.isHidden = true } @@ -68,6 +68,12 @@ class InteractionView: UIView { }) .disposed(by: disposeBag) + shareBtn.rx.tap + .subscribe(onNext: { [weak self] _ in + self?.onShare?() + }) + .disposed(by: disposeBag) + // 绑定表情数据到 collectionView emojiRelay .map { [SectionModel(model: "", items: $0)] } @@ -115,7 +121,7 @@ class InteractionView: UIView { interestBg.isHidden = isFun emojiRelay.accept(isFun ? UIView.funEmojiFileNames : UIView.emojiFileNames) } - + private lazy var dataSource: RxCollectionViewSectionedReloadDataSource> = { RxCollectionViewSectionedReloadDataSource> { _, collectionView, indexPath, name in let cell: EmojiPanelCell = collectionView.dequeueReusableCell(for: indexPath) @@ -130,7 +136,7 @@ class InteractionView: UIView { (cell as? EmojiPanelCell)?.stopAnimation() } } - + private func setupUI() { addSubview(infoView) infoView.addSubview(headerBgView) @@ -150,109 +156,109 @@ class InteractionView: UIView { emojiView.addSubview(segmentView) emojiView.addSubview(emojiCollectionView) emojiView.addSubview(emojiPageControl) - + infoView.layoutChain.edges() - + headerBgView.layoutChain .edges(excludingEdge: .bottom) .height(134) - + lineView.layoutChain .top(13) .width(36) .height(4) .centerX() - + avaterImgView.layoutChain .top(25) .left(25) .width(50) .height(50) - + batteryInfoView.layoutChain .leftToView(avaterImgView) .rightToView(avaterImgView) .bottomToView(avaterImgView) .height(12) - + cornerView.layoutChain.edges() - + batteryIcon.layoutChain .left(7) .centerY() .width(16) .height(8) - + batteryView.layoutChain .topToView(batteryIcon) .leftToView(batteryIcon, offset: -1) .bottomToView(batteryIcon) - + batteryLab.layoutChain .leftToRightOfView(batteryIcon, offset: 4) .right(5) .centerY() - + nameLab.layoutChain .topToView(avaterImgView, offset: 8) .leftToRightOfView(avaterImgView, offset: 15) - + locationLab.layoutChain .topToBottomOfView(nameLab) .leftToView(nameLab) - + shareBtn.layoutChain .right(15) .centerY(avaterImgView) .width(30) .height(30) - + navigateBtn.layoutChain .rightToLeftOfView(shareBtn, offset: -10) .centerY(avaterImgView) .width(30) .height(30) - + emojiView.layoutChain .topToBottomOfView(batteryInfoView, offset: 21) .edgesHorzontal(15) .height(276) - + emojiBgView.layoutChain.edges() - + segmentView.layoutChain .edges(excludingEdge: .bottom) .height(56) - + emojiPageControl.layoutChain .centerX() .height(28) .bottom(13) - + emojiCollectionView.layoutChain .top(51) .edgesHorzontal() .height(180) } - + lazy var infoView: UIView = { let view = UIView() view.backgroundColor = .white return view }() - + lazy var headerBgView: UIImageView = { let view = UIImageView(image: UIImage(named: "Home/interaction_header")) return view }() - + lazy var lineView: UIView = { let view = UIView() view.backgroundColor = .white view.cornerRadius = 2 return view }() - + lazy var avaterImgView: UIImageView = { let view = UIImageView() view.backgroundColor = .lightGray @@ -260,7 +266,7 @@ class InteractionView: UIView { view.cornerRadius = 25 return view }() - + lazy var batteryInfoView: UIView = { let view = UIView() view.backgroundColor = .clear @@ -271,48 +277,48 @@ class InteractionView: UIView { view.isHidden = true return view }() - + lazy var cornerView: UIView = { let view = UIView() view.backgroundColor = .white view.cornerRadius = 6 return view }() - + lazy var batteryView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#75E582") return view }() - + lazy var batteryIcon: UIImageView = { let view = UIImageView() view.backgroundColor = .clear view.image = UIImage(named: "Home/battery") return view }() - + lazy var batteryLab: UILabel = { let label = UILabel() label.textColor = UIColor(hexStr: "#D4D4D4") label.font = .systemFont(ofSize: 6, weight: .medium) return label }() - + lazy var nameLab: UILabel = { let label = UILabel() label.textColor = UIColor(hexStr: "#0F2846") label.font = .systemFont(ofSize: 14, weight: .semibold) return label }() - + lazy var locationLab: UILabel = { let label = UILabel() label.textColor = UIColor(hexStr: "#8D8D8D") label.font = .systemFont(ofSize: 10, weight: .regular) return label }() - + lazy var navigateBtn: UIButton = { let btn = UIButton(type: .custom) btn.backgroundColor = .white @@ -321,7 +327,7 @@ class InteractionView: UIView { btn.extendEdgeInsets = UIEdgeInsets(top: 30, left: 30, bottom: 30, right: 0) return btn }() - + lazy var shareBtn: UIButton = { let btn = UIButton(type: .custom) btn.backgroundColor = .white @@ -330,36 +336,36 @@ class InteractionView: UIView { btn.extendEdgeInsets = UIEdgeInsets(top: 30, left: 0, bottom: 30, right: 15) return btn }() - + lazy var emojiView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#E3F7FE")//.clear view.cornerRadius = 15 return view }() - + lazy var emojiBgView: UIView = { let view = UIImageView()//UIImageView(image: UIImage(named: "Home/emoji_bg")) view.contentMode = .scaleAspectFill return view }() - + lazy var segmentView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#E3F7FE") - + view.addSubview(normalBg) view.addSubview(interestBg) normalBg.layoutChain .top().left() .height(56) .widthToHeight(220/56) - + interestBg.layoutChain .top().right() .height(56) .widthToHeight(220/56) - + let normalLab = UILabel() normalLab.text = "常规表情" normalLab.font = .systemFont(ofSize: 12, weight: .semibold) @@ -369,7 +375,7 @@ class InteractionView: UIView { normalLab.layoutChain .top(11) .left().rightToCenterXOfView(view) - + let interesView = UIView() interesView.backgroundColor = .clear view.addSubview(interesView) @@ -377,81 +383,81 @@ class InteractionView: UIView { .top(11) .right().leftToCenterXOfView(view) .height(18) - + let interestLab = UILabel() interestLab.text = "趣味表情" interestLab.font = .systemFont(ofSize: 12, weight: .semibold) interestLab.textColor = .black interestLab.textAlignment = .center - + interesView.addSubview(interestLab) interestLab.layoutChain .top() .centerX() - + interesView.addSubview(lockView) lockView.layoutChain .leftToRightOfView(interestLab, offset: 4) .centerY(interestLab) .width(18) .height(18) - + view.addSubview(emojiNormalBtn) emojiNormalBtn.layoutChain .top() .left() .rightToCenterXOfView(view) .height(40) - + view.addSubview(emojiFunBtn) emojiFunBtn.layoutChain .top() .right() .leftToCenterXOfView(view) .height(40) - + return view }() - + lazy var normalBg: UIImageView = { let view = UIImageView(image: UIImage(named: "Home/emoji_normal")) view.contentMode = .scaleAspectFill view.isHidden = true return view }() - + lazy var interestBg: UIImageView = { let view = UIImageView(image: UIImage(named: "Home/emoji_interest")) view.contentMode = .scaleAspectFill return view }() - + lazy var emojiNormalBtn: UIButton = { let btn = UIButton() btn.backgroundColor = .clear return btn }() - + lazy var emojiFunBtn: UIButton = { let btn = UIButton() btn.backgroundColor = .clear return btn }() - + lazy var lockView: UIView = { let view = UIView() view.backgroundColor = .white view.cornerRadius = 9 - + let icon = UIImageView(image: UIImage(named: "Home/lock")) view.addSubview(icon) icon.layoutChain .centerX() .centerY() - + return view }() - + lazy var emojiCollectionView: UICollectionView = { let layout = CollectionHFlowLayout() let hSpacing: CGFloat = (kScreenWidth - 30 - CGFloat(InteractionView.emojiCols) * 50) / CGFloat(InteractionView.emojiCols + 1) @@ -471,9 +477,10 @@ class InteractionView: UIView { cv.delegate = self return cv }() - + var onNavigate: (() -> Void)? var onSendEmote: ((Int) -> Void)? + var onShare: (() -> Void)? var currentMember: CircleMember? lazy var emojiPageControl: UIPageControl = { @@ -483,7 +490,7 @@ class InteractionView: UIView { pc.pageIndicatorTintColor = UIColor(hexStr: "#7AD6FF", alpha: 0.4) return pc }() - + override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .clear @@ -559,17 +566,17 @@ final class EmojiPanelCell: UICollectionViewCell { v.loopMode = .loop return v }() - + lazy var lockView: UIView = { let view = UIView() view.backgroundColor = .clear view.isHidden = true - + let icon = UIImageView(image: UIImage(named: "Home/emoji_lock")) view.addSubview(icon) icon.layoutChain .edges() - + return view }() @@ -603,7 +610,7 @@ final class EmojiPanelCell: UICollectionViewCell { self.lottieView.play() } } - + lockView.isHidden = AppContextManager.shared.vip > 1 } @@ -636,7 +643,7 @@ extension UIView { .map { $0.replacingOccurrences(of: ".json", with: "") } .sorted() }() - + static var funEmojiFileNames: [String] = { let paths = Bundle.main.paths(forResourcesOfType: "json", inDirectory: nil) return paths diff --git a/QuickLocation/Section/PopupWindow/PromotionalActivitiesVC.swift b/QuickLocation/Section/PopupWindow/PromotionalActivitiesVC.swift index 293a09b..ed672b2 100644 --- a/QuickLocation/Section/PopupWindow/PromotionalActivitiesVC.swift +++ b/QuickLocation/Section/PopupWindow/PromotionalActivitiesVC.swift @@ -10,10 +10,37 @@ import RxSwift import RxCocoa import RxDataSources +extension PromotionalActivitiesItem: Equatable { + static func == (lhs: Self, rhs: Self) -> Bool { + switch (lhs, rhs) { + case (.recommend(let a), .recommend(let b)): return a == b + case (.normal(let a), .normal(let b)): return a == b + default: return false + } + } +} + +extension PromotionalActivitiesItem: IdentifiableType { + typealias Identity = String + var identity: String { + switch self { + case .recommend(let model): return model.goods_id + case .normal(let model): return model.goods_id + } + } +} + class PromotionalActivitiesVC: BaseViewController { fileprivate var rootView: PromotionalActivitiesView! var onDismiss: (() -> Void)? + private var selectedGoodsId: String = "" + private let itemsRelay = BehaviorRelay<[PromotionalActivitiesItem]>(value: []) + + var items: [PromotionalActivitiesItem] { + get { itemsRelay.value } + set { itemsRelay.accept(newValue) } + } override func loadView() { rootView = PromotionalActivitiesView(frame: UIScreen.main.bounds) @@ -23,8 +50,50 @@ class PromotionalActivitiesVC: BaseViewController { override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view. + setupTableView() requestRechargeInfo() + + rootView.confirmBtn.rx.tap.subscribe(onNext: { _ in + self.requestOrderPayParams() + }).disposed(by: disposeBag) + } + + private func setupTableView() { + typealias Section = SectionModel + let dataSource = RxTableViewSectionedReloadDataSource
{ _, tableView, indexPath, item in + let isSelected = item.identity == self.selectedGoodsId + switch item { + case .recommend(let model): + let cell: PromotionalRecommendCell = tableView.dequeueReusableCell(for: indexPath) + cell.configure(model: model, isSelected: isSelected) + return cell + case .normal(let model): + let cell: PromotionalNormalCell = tableView.dequeueReusableCell(for: indexPath) + cell.configure(model: model, isSelected: isSelected) + return cell + } + } + + itemsRelay + .map { [SectionModel(model: "", items: $0)] } + .bind(to: rootView.tableView.rx.items(dataSource: dataSource)) + .disposed(by: disposeBag) + + itemsRelay + .subscribe(onNext: { [weak self] items in + self?.rootView.tableView.layoutChain.height(CGFloat(items.count) * 106 + 5) + self?.rootView.scrollContentView.layoutIfNeeded() + }) + .disposed(by: disposeBag) + + rootView.tableView.rx.itemSelected + .subscribe(onNext: { [weak self] indexPath in + guard let self = self else { return } + let item = self.itemsRelay.value[indexPath.row] + self.selectedGoodsId = item.identity + self.rootView.tableView.reloadData() + }) + .disposed(by: disposeBag) } override func viewDidDisappear(_ animated: Bool) { @@ -39,7 +108,103 @@ class PromotionalActivitiesVC: BaseViewController { DLToast.showLoading() OrderService.rechargeInfo(type: "member").subscribe(onNext: { [weak self] response in guard let self = self else { return } - self.rootView.startCountdown(endTime: 1769875199) + if let checkedId = response.list.first(where: { $0.checked })?.goods_id { + self.selectedGoodsId = checkedId + } + let items = response.list.map { model in + model.goods_id == "20042" + ? PromotionalActivitiesItem.recommend(model: model) + : PromotionalActivitiesItem.normal(model: model) + } + self.items = items + self.rootView.startCountdown() }).disposed(by: disposeBag) } + + private func requestOrderPayParams() { + guard let systemConfig = AppContextManager.shared.systemConfig, let timeConfig = systemConfig.timeConfig else { + return + } + + if timeConfig.pay_pop_type == 3 { + IAPManager.shared.purchase(goodsId: selectedGoodsId) { [weak self] success, msg in + guard let self = self else { return } + if success { + self.showPayResultPop(showCloseBtn: false, + status: true, + title: "支付成功", + message: "恭喜您!成功开通!", + confirmText: "确定", confirmBlock: { + AppRouter.shared.popOrDismiss() + }, cancelBlock: { }) + } else { + self.showPayResultPop(status: false, + title: "支付失败", + message: msg ?? "很抱歉,请您重新支付!", + confirmText: "重新支付", confirmBlock: { }, + cancelText: "取消", cancelBlock: { + AppRouter.shared.popOrDismiss() + }) + } + } + return + } + + let payType = timeConfig.pay_pop_type == 1 ? "alipay" : "weixin" + dl.showLoading() + OrderService.orderPayParams(goodsId: selectedGoodsId, + payType: payType).subscribe(onNext: { response in + self.dl.dismiss() + guard let data = response.data else { return } + + if payType == "weixin" { // 微信 +// WXApi.registerApp(model.appId, universalLink: AppSettings.kAppsUniversalLink) +// +// let request: PayReq = PayReq() +// request.partnerId = model.partnerId +// request.prepayId = model.prepayId +// request.package = model.packageValue +// request.nonceStr = model.nonceStr +// request.timeStamp = UInt32(model.timeStamp) ?? 0 +// request.sign = model.sign +// WXApi.send(request) + } + else if payType == "alipay" { // 支付宝 + if let payParam = data["payParam"] as? String, let appId = data["payParam"] as? String { + AlipaySDK.defaultService().payOrder(payParam, fromScheme: "Alipay\(appId)") { resultDic in + print("支付宝callback -> \(resultDic)") + guard let result = resultDic, + let resultStatus = result["resultStatus"] as? String else { return } + /** + 9000 订单支付成功。 + 8000 正在处理中,支付结果未知(有可能已经支付成功),请查询商家订单列表中订单的支付状态。 + 4000 订单支付失败。 + 5000 重复请求。 + 6001 用户中途取消。 + 6002 网络连接出错。 + 6004 支付结果未知(有可能已经支付成功),请查询商家订单列表中订单的支付状态。 + 其它 其它支付错误。 + */ + if resultStatus == "9000" { + self.showPayResultPop(showCloseBtn: false, + status: true, + title: "支付成功", + message: "恭喜您!成功开通\("")", + confirmText: "确定", confirmBlock: { + AppRouter.shared.popOrDismiss() + }, cancelBlock: { }) + } + else { + self.showPayResultPop(status: false, + title: "支付失败", + message: "很抱歉,请您重新支付!", + confirmText: "重新支付", confirmBlock: { }, cancelText: "取消", cancelBlock: { + AppRouter.shared.popOrDismiss() + }) + } + } + } + } + }, onError: { _ in }).disposed(by: disposeBag) + } } diff --git a/QuickLocation/Section/PopupWindow/PromotionalActivitiesView.swift b/QuickLocation/Section/PopupWindow/PromotionalActivitiesView.swift index 127cbfd..0be5959 100644 --- a/QuickLocation/Section/PopupWindow/PromotionalActivitiesView.swift +++ b/QuickLocation/Section/PopupWindow/PromotionalActivitiesView.swift @@ -9,6 +9,11 @@ import UIKit import RxSwift import RxCocoa +enum PromotionalActivitiesItem { + case recommend(model: VipExpenseModel) + case normal(model: VipExpenseModel) +} + class PromotionalActivitiesView: UIView { var disposeBag = DisposeBag() @@ -17,7 +22,6 @@ class PromotionalActivitiesView: UIView { backBtn.rx.tap.subscribe(onNext: { _ in VipWaivePopView.show() }).disposed(by: disposeBag) - } private func setupUI() { @@ -121,13 +125,18 @@ class PromotionalActivitiesView: UIView { .leftToRightOfView(iconView, offset: 4) .centerY(iconView) + scrollContentView.addSubview(tableView) + tableView.layoutChain + .topToBottomOfView(titleLab, offset: 0) + .edgesHorzontal() + let agreementView = UIView() infoView.addSubview(agreementView) agreementView.layoutChain - .topToBottomOfView(titleLab, offset: 18) + .topToBottomOfView(tableView, offset: 15) .left(15) .right(15) - .height(30) + .height(16) agreementView.addSubview(agreementCheckBtn) agreementCheckBtn.layoutChain @@ -138,7 +147,7 @@ class PromotionalActivitiesView: UIView { agreementView.addSubview(agreementTextView) agreementTextView.layoutChain - .leftToRightOfView(agreementCheckBtn, offset: 6) + .leftToRightOfView(agreementCheckBtn, offset: 3) .right(0) .centerY() .bottom(0) @@ -161,12 +170,24 @@ class PromotionalActivitiesView: UIView { return view }() + lazy var tableView: UITableView = { + let tableView = UITableView(frame: .zero, style: .plain) + tableView.backgroundColor = .clear + tableView.separatorStyle = .none + tableView.estimatedRowHeight = 106 + tableView.isScrollEnabled = false + tableView.showsVerticalScrollIndicator = false + tableView.register(PromotionalNormalCell.self) + tableView.register(PromotionalRecommendCell.self) + return tableView + }() + lazy var bottomView: UIView = { let view = UIView() view.backgroundColor = .white - view.addSubview(countdownLabel) - countdownLabel.layoutChain + view.addSubview(countdownContainer) + countdownContainer.layoutChain .top(11) .centerX() @@ -186,6 +207,7 @@ class PromotionalActivitiesView: UIView { btn.rx.tap.subscribe(onNext: { [weak btn] in btn?.isSelected.toggle() }).disposed(by: disposeBag) + btn.isSelected = true return btn }() @@ -212,9 +234,59 @@ class PromotionalActivitiesView: UIView { return tv }() - lazy var countdownLabel: UILabel = { + lazy var countdownContainer: UIStackView = { + let stack = UIStackView(arrangedSubviews: [countdownPrefixLabel, countdownMinLabel, countdownColonLabel, countdownSecLabel, countdownSuffixLabel]) + stack.axis = .horizontal + stack.alignment = .center + stack.spacing = 0 + return stack + }() + + lazy var countdownPrefixLabel: UILabel = { let label = UILabel() + label.font = .systemFont(ofSize: 12) + label.textColor = UIColor(hexStr: "#FD501B") + label.text = "优惠活动倒计时 " + return label + }() + + lazy var countdownMinLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 12) + label.textColor = .white + label.backgroundColor = UIColor(hexStr: "#FD501B") + label.layer.cornerRadius = 2 + label.clipsToBounds = true label.textAlignment = .center + label.text = "15" + return label + }() + + lazy var countdownColonLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 12) + label.textColor = UIColor(hexStr: "#FD501B") + label.text = ":" + return label + }() + + lazy var countdownSecLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 12) + label.textColor = .white + label.backgroundColor = UIColor(hexStr: "#FD501B") + label.layer.cornerRadius = 2 + label.clipsToBounds = true + label.textAlignment = .center + label.text = "00" + return label + }() + + lazy var countdownSuffixLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 12) + label.textColor = UIColor(hexStr: "#FD501B") + label.text = " 限时特惠" return label }() @@ -228,11 +300,11 @@ class PromotionalActivitiesView: UIView { return btn }() - private var countdownEndTime: TimeInterval = 0 + private var countdownRemaining: Int = 15 * 60 private var countdownTimer: Timer? - func startCountdown(endTime: TimeInterval) { - countdownEndTime = endTime + func startCountdown() { + countdownRemaining = 15 * 60 updateCountdown() countdownTimer?.invalidate() countdownTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in @@ -246,38 +318,17 @@ class PromotionalActivitiesView: UIView { } 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) + if countdownRemaining > 0 { + countdownRemaining -= 1 } - attr.append(NSAttributedString(string: timeStr, attributes: timeStyle)) - - attr.append(NSAttributedString(string: " 限时特惠", attributes: normalStyle)) - - countdownLabel.attributedText = attr + else { + AppRouter.shared.popOrDismiss() + return + } + let min = countdownRemaining / 60 + let sec = countdownRemaining % 60 + countdownMinLabel.text = String(format: "%02d", min) + countdownSecLabel.text = String(format: "%02d", sec) } override func layoutSubviews() { @@ -319,3 +370,287 @@ extension PromotionalActivitiesView: UITextViewDelegate { return false } } + +// MARK: - PromotionalNormalCell +class PromotionalNormalCell: UITableViewCell { + + func configure(model: VipExpenseModel, isSelected: Bool) { + priceLab.text = model.price + originPriceLab.text = "原价" + model.origin_price + originPriceLab.setupStrikethroughStyle() + goodsNameLab.text = model.goods_name + tipsLab.text = model.tips2 + bgView.image = UIImage(named: isSelected ? "PromotionalActivities/normal_on_bg" : "PromotionalActivities/normal_bg") + priceLab.textColor = isSelected ? UIColor(hexStr: "#FF383C") : ThemeManager.shared.color.titleAuxColor + unitLab.textColor = isSelected ? UIColor(hexStr: "#FF383C") : ThemeManager.shared.color.titleAuxColor + } + + override init(style: CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + selectionStyle = .none + backgroundColor = .clear + setupSubviews() + } + + private func setupSubviews() { + contentView.addSubview(bgView) + bgView.addSubview(unitLab) + bgView.addSubview(priceLab) + bgView.addSubview(originPriceLab) + bgView.addSubview(goodsNameLab) + bgView.addSubview(tipsLab) + + bgView.layoutChain + .top(22) + .edgesHorzontal(15) + .bottom() + + priceLab.layoutChain + .leftToRightOfView(unitLab, offset: 0) + .centerY() + + unitLab.layoutChain + .left(32) + .centerY(bgView, offset: 9) + + originPriceLab.layoutChain + .topToBottomOfView(priceLab, offset: -5) + .centerX(priceLab) + + goodsNameLab.layoutChain + .leftToRightOfView(priceLab, offset: 27) + .bottomToCenterYOfView(bgView) + + tipsLab.layoutChain + .leftToView(goodsNameLab) + .topToCenterYOfView(bgView, offset: 3) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + + lazy var bgView: UIImageView = { + let view = UIImageView() + view.backgroundColor = .clear + view.contentMode = .scaleAspectFill + + return view + }() + + lazy var priceLab: UILabel = { + let label = UILabel() + label.textColor = UIColor(hexStr: "#FF383C") + label.font = .systemFont(ofSize: 40, weight: .bold) + return label + }() + + lazy var unitLab: UILabel = { + let label = UILabel() + label.text = "¥" + label.textColor = UIColor(hexStr: "#FF383C") + label.font = .systemFont(ofSize: 12, weight: .regular) + return label + }() + + lazy var originPriceLab: UILabel = { + let label = UILabel() + label.textColor = UIColor(hexStr: "#999999") + label.font = .systemFont(ofSize: 10, weight: .regular) + return label + }() + + lazy var goodsNameLab: UILabel = { + let label = UILabel() + label.textColor = ThemeManager.shared.color.titleAuxColor + label.font = .systemFont(ofSize: 18, weight: .semibold) + return label + }() + + lazy var tipsLab: UILabel = { + let label = UILabel() + label.textColor = ThemeManager.shared.color.contentColor + label.font = .systemFont(ofSize: 14, weight: .regular) + return label + }() +} + +// MARK: - PromotionalRecommendCell +class PromotionalRecommendCell: UITableViewCell { + + func configure(model: VipExpenseModel, isSelected: Bool) { + tipsTitleLab.text = model.tips + priceLab.text = model.price + originPriceLab.text = "原价" + model.origin_price + originPriceLab.setupStrikethroughStyle() + goodsNameLab.text = model.goods_name + tipsLab.text = model.tips2 + bgView.image = UIImage(named: isSelected ? "PromotionalActivities/recommend_on_bg" : "PromotionalActivities/recommend_bg") + priceLab.textColor = isSelected ? UIColor(hexStr: "#FF383C") : ThemeManager.shared.color.titleAuxColor + unitLab.textColor = isSelected ? UIColor(hexStr: "#FF383C") : ThemeManager.shared.color.titleAuxColor + } + + override init(style: CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + selectionStyle = .none + backgroundColor = .clear + setupSubviews() + } + + private func setupSubviews() { + contentView.addSubview(bgView) + bgView.addSubview(unitLab) + bgView.addSubview(priceLab) + bgView.addSubview(originPriceLab) + bgView.addSubview(goodsNameLab) + bgView.addSubview(tipsLab) + bgView.addSubview(recommendLab) + bgView.addSubview(recommendIcon) + bgView.addSubview(tipsTitleView) + tipsTitleView.addSubview(tipsTitleLab) + + bgView.layoutChain + .top(22) + .edgesHorzontal(15) + .bottom() + + tipsTitleView.layoutChain + .topToView(bgView, offset: -8) + .leftToView(bgView, offset: -6) + .width(94) + .height(35) + + tipsTitleLab.layoutChain + .centerX() + .top(5) + + priceLab.layoutChain + .leftToRightOfView(unitLab, offset: 0) + .centerY() + + unitLab.layoutChain + .left(32) + .centerY(bgView, offset: 9) + + originPriceLab.layoutChain + .topToBottomOfView(priceLab, offset: -5) + .centerX(priceLab) + + goodsNameLab.layoutChain + .leftToRightOfView(priceLab, offset: 27) + .bottomToCenterYOfView(bgView, offset: -5) + + tipsLab.layoutChain + .leftToView(goodsNameLab) + .topToCenterYOfView(bgView, offset: 8) + + recommendLab.layoutChain + .top(8) + .right(24) + + recommendIcon.layoutChain + .rightToLeftOfView(recommendLab, offset: -3) + .centerY(recommendLab) + .width(13) + .height(11) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + + lazy var bgView: UIImageView = { + let view = UIImageView() + view.backgroundColor = .clear + view.contentMode = .scaleAspectFill + + return view + }() + + lazy var priceLab: UILabel = { + let label = UILabel() + label.textColor = UIColor(hexStr: "#FF383C") + label.font = .systemFont(ofSize: 40, weight: .bold) + return label + }() + + lazy var unitLab: UILabel = { + let label = UILabel() + label.text = "¥" + label.textColor = UIColor(hexStr: "#FF383C") + label.font = .systemFont(ofSize: 12, weight: .regular) + return label + }() + + lazy var originPriceLab: UILabel = { + let label = UILabel() + label.textColor = UIColor(hexStr: "#999999") + label.font = .systemFont(ofSize: 10, weight: .regular) + return label + }() + + lazy var goodsNameLab: UILabel = { + let label = UILabel() + label.textColor = ThemeManager.shared.color.titleAuxColor + label.font = .systemFont(ofSize: 18, weight: .semibold) + return label + }() + + lazy var tipsTitleView: UIImageView = { + let view = UIImageView() + view.backgroundColor = .clear + view.image = UIImage(named: "PromotionalActivities/tips_bg") + return view + }() + + lazy var tipsTitleLab: UILabel = { + let label = UILabel() + label.textColor = .white + label.font = .systemFont(ofSize: 12, weight: .semibold) + return label + }() + + lazy var tipsLab: UILabel = { + let label = UILabel() + label.textColor = ThemeManager.shared.color.contentColor + label.font = .systemFont(ofSize: 14, weight: .regular) + return label + }() + + lazy var recommendIcon: UIImageView = { + let view = UIImageView() + view.contentMode = .scaleAspectFill + view.image = UIImage(named: "PromotionalActivities/recommend_icon") + return view + }() + + lazy var recommendLab: UILabel = { + let label = UILabel() + label.text = "80%的用户选择" + label.textColor = .white + label.font = .systemFont(ofSize: 12, weight: .medium) + return label + }() +} diff --git a/QuickLocation/Section/Share/SharePopView.swift b/QuickLocation/Section/Share/SharePopView.swift new file mode 100644 index 0000000..bd770b8 --- /dev/null +++ b/QuickLocation/Section/Share/SharePopView.swift @@ -0,0 +1,199 @@ +// +// SharePopView.swift +// QuickLocation +// +// Created by 八条 on 2026/7/3. +// + +import UIKit + +class SharePopView: UIView { + + private static let shared = SharePopView(frame: CGRect(origin: .zero, size: kScreenSize)) + private var shareURL: String = "" + + private let contentHeight: CGFloat = 191 + kSafeBottomMargin + + static func show(shareURL: String) { + guard let superView = kKeyWindow else { return } + + if SharePopView.shared.superview != nil { + SharePopView.shared.dismiss(animated: false) + } + + SharePopView.shared.shareURL = shareURL + SharePopView.shared.bgView.alpha = 0 + superView.addSubview(SharePopView.shared) + superView.bringSubviewToFront(SharePopView.shared) + + SharePopView.shared.contentView.transform = CGAffineTransform(translationX: 0, y: SharePopView.shared.contentHeight) + UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut) { + SharePopView.shared.bgView.alpha = 1 + SharePopView.shared.contentView.transform = .identity + } + } + + private func dismiss(animated: Bool = true) { + if animated { + UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseIn) { + self.bgView.alpha = 0 + self.contentView.transform = CGAffineTransform(translationX: 0, y: self.contentHeight) + } completion: { _ in + self.removeFromSuperview() + } + } else { + removeFromSuperview() + } + } + + @objc private func handleBgTap() { + dismiss() + } + + @objc private func handleWeChatTap() { + shareToWeChat(scene: 0) + } + + @objc private func handleMomentsTap() { + shareToWeChat(scene: 1) + } + + @objc private func handleLinkTap() { + UIPasteboard.general.string = shareURL + DLToast.showSuccess(text: "链接已复制") + dismiss() + } + + private func shareToWeChat(scene: Int32) { + let message = WXMediaMessage() + message.title = kAppName + let webpage = WXWebpageObject() + webpage.webpageUrl = shareURL + message.mediaObject = webpage + + let req = SendMessageToWXReq() + req.bText = false + req.message = message + req.scene = scene + WXApi.send(req) + + dismiss() + } + + // MARK: - UI + + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = .clear + + addSubview(bgView) + bgView.addSubview(contentView) + contentView.addSubview(titleLabel) + contentView.addSubview(shareStack) + contentView.addSubview(closeBtn) + + bgView.layoutChain.edges() + contentView.layoutChain + .edges(excludingEdge: .top) + .height(contentHeight) + + titleLabel.layoutChain + .top(20) + .centerX() + + closeBtn.layoutChain + .centerY(titleLabel) + .right(20) + .width(20) + .height(20) + + shareStack.layoutChain + .topToBottomOfView(titleLabel, offset: 30) + .centerX() + + let tap = UITapGestureRecognizer(target: self, action: #selector(handleBgTap)) + bgView.addGestureRecognizer(tap) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Lazy Views + + private lazy var bgView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.5) + return view + }() + + private lazy var contentView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.layer.cornerRadius = 26 + view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + view.clipsToBounds = true + return view + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.text = "分享" + label.font = .systemFont(ofSize: 16, weight: .medium) + label.textColor = UIColor(hexStr: "#333333") + return label + }() + + private lazy var shareStack: UIStackView = { + let stack = UIStackView(arrangedSubviews: [ + makeShareItem(icon: "Share/friend", title: "微信", action: #selector(handleWeChatTap)), + makeShareItem(icon: "Share/moments", title: "朋友圈", action: #selector(handleMomentsTap)), + makeShareItem(icon: "Share/url", title: "链接", action: #selector(handleLinkTap)) + ]) + stack.axis = .horizontal + stack.spacing = 50 + stack.alignment = .center + return stack + }() + + private lazy var closeBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setImage(UIImage(named: "Group/close"), for: .normal) + btn.addTarget(self, action: #selector(handleBgTap), for: .touchUpInside) + btn.extendEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 20, right: 20) + return btn + }() + + private func makeShareItem(icon: String, title: String, action: Selector) -> UIView { + let container = UIView() + + let imgView = UIImageView(image: UIImage(named: icon)) + imgView.contentMode = .scaleAspectFit + container.addSubview(imgView) + imgView.layoutChain + .top(0) + .centerX() + .width(50) + .height(50) + + let label = UILabel() + label.text = title + label.font = .systemFont(ofSize: 12) + label.textColor = UIColor(hexStr: "#666666") + label.textAlignment = .center + container.addSubview(label) + label.layoutChain + .topToBottomOfView(imgView, offset: 8) + .centerX() + .bottom(0) + + container.layoutChain.width(60).height(78) + + let btn = UIButton(type: .custom) + container.addSubview(btn) + btn.layoutChain.edges() + btn.addTarget(self, action: action, for: .touchUpInside) + + return container + } +} diff --git a/QuickLocation/Section/VipRecharge/VipRechargeVC.swift b/QuickLocation/Section/VipRecharge/VipRechargeVC.swift index 46d5d31..9081cb0 100644 --- a/QuickLocation/Section/VipRecharge/VipRechargeVC.swift +++ b/QuickLocation/Section/VipRecharge/VipRechargeVC.swift @@ -18,7 +18,7 @@ class VipRechargeVC: BaseViewController { rootView = VipRechargeView(frame: UIScreen.main.bounds) view = rootView } - + private var viewModel = VipRechargeVM() override func viewDidLoad() { @@ -33,19 +33,19 @@ class VipRechargeVC: BaseViewController { viewModel.output.sectionedItems .bind(to: rootView.expenseCollectionView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag) - + viewModel.output.vipRightItems .bind(to: rootView.vipRightsCollectionView.rx.items(dataSource: vipRightsDataSource)) .disposed(by: disposeBag) - + viewModel.createCount.asObservable() .bind(to: rootView.createCountLab.rx.text) .disposed(by: disposeBag) - + viewModel.groupCount.asObservable() .bind(to: rootView.groupCountLab.rx.text) .disposed(by: disposeBag) - + viewModel.joinCount.asObservable() .bind(to: rootView.joinCountLab.rx.text) .disposed(by: disposeBag) @@ -55,7 +55,7 @@ class VipRechargeVC: BaseViewController { rootView.payBtnView.rx.tapGesture.subscribe(onNext: { _ in self.requestOrderPayParams() }).disposed(by: disposeBag) - + Observable.zip( rootView.expenseCollectionView.rx.itemSelected, rootView.expenseCollectionView.rx.modelSelected(VipExpenseModel.self) @@ -67,7 +67,7 @@ class VipRechargeVC: BaseViewController { }) .disposed(by: disposeBag) } - + // MARK: - dataSource private lazy var dataSource: RxCollectionViewSectionedReloadDataSource = { RxCollectionViewSectionedReloadDataSource { datasource, collectionView, indexPath, model in @@ -76,7 +76,7 @@ class VipRechargeVC: BaseViewController { return cell } }() - + private lazy var vipRightsDataSource: RxCollectionViewSectionedReloadDataSource = { RxCollectionViewSectionedReloadDataSource { datasource, collectionView, indexPath, model in let cell: VipRightsCell = collectionView.dequeueReusableCell(for: indexPath) @@ -96,19 +96,45 @@ class VipRechargeVC: BaseViewController { self.rootView.discountLab.text = self.viewModel.discountPriceString }).disposed(by: disposeBag) } - + // MARK: - API支付参数 private func requestOrderPayParams() { - DLToast.showLoading() let types = viewModel.payType.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) } + let payType = types[rootView.selectedPayTypeTag] + + if payType == "apple" { + IAPManager.shared.purchase(goodsId: viewModel.goodsId) { [weak self] success, msg in + guard let self = self else { return } + if success { + self.showPayResultPop(showCloseBtn: false, + status: true, + title: "支付成功", + message: "恭喜您!成功开通\(self.viewModel.goodsName)", + confirmText: "确定", confirmBlock: { + AppRouter.shared.popOrDismiss() + }, cancelBlock: { }) + } else { + self.showPayResultPop(status: false, + title: "支付失败", + message: msg ?? "很抱歉,请您重新支付!", + confirmText: "重新支付", confirmBlock: { }, + cancelText: "取消", cancelBlock: { + AppRouter.shared.popOrDismiss() + }) + } + } + return + } + + DLToast.showLoading() OrderService.orderPayParams(goodsId: viewModel.goodsId, payType: types[rootView.selectedPayTypeTag]).subscribe(onNext: { response in guard let data = response.data else { return } - + let payType = types[self.rootView.selectedPayTypeTag] if payType == "weixin" { // 微信 // WXApi.registerApp(model.appId, universalLink: AppSettings.kAppsUniversalLink) -// +// // let request: PayReq = PayReq() // request.partnerId = model.partnerId // request.prepayId = model.prepayId diff --git a/QuickLocation/Section/VipRecharge/VipRechargeView.swift b/QuickLocation/Section/VipRecharge/VipRechargeView.swift index 5269344..ebc3ae5 100644 --- a/QuickLocation/Section/VipRecharge/VipRechargeView.swift +++ b/QuickLocation/Section/VipRecharge/VipRechargeView.swift @@ -10,23 +10,23 @@ import RxSwift import RxCocoa class VipRechargeView: UIView { - + var disposeBag = DisposeBag() - + private func setupRx() { backBtn.rx.tap.subscribe(onNext: { _ in VipWaivePopView.show() }).disposed(by: disposeBag) - + agreementLab.rx.tapGesture.subscribe { _ in AppRouter.push(Route.web, userInfo: ["url": URLManager.shared.userAgreementUrl]) }.disposed(by: disposeBag) - + vipRightsDetailView.rx.tapGesture.subscribe { _ in AppRouter.push(Route.vipRights) }.disposed(by: disposeBag) } - + private func setupUI() { addSubview(scrollView) scrollView.addSubview(scrollContentView) @@ -37,119 +37,119 @@ class VipRechargeView: UIView { scrollContentView.addSubview(vipRightsView) scrollContentView.addSubview(agreementLab) scrollContentView.addSubview(tipsLab) - + vipRightsView.addSubview(vipRightsTitleView) vipRightsView.addSubview(groupCountView) vipRightsView.addSubview(vipRightsCollectionView) - + addSubview(bottomView) bottomView.addSubview(payTypeStackView) bottomView.addSubview(payBtnView) bottomView.addSubview(payPriceView) - + 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) - + bottomView.layoutChain .edgesHorzontal() .heightToWidth(144/375) .bottom() - + payBtnView.layoutChain .edgesHorzontal(16) .heightToWidth(50/343) .centerY() - + payTypeStackView.layoutChain .top(13) .edgesHorzontal(16) .bottomToTopOfView(payBtnView) - + payPriceView.layoutChain .left(32) .centerY(payBtnView, offset: -7) .height(30) - + scrollView.layoutChain .edges(excludingEdge: .bottom) .bottomToTopOfView(bottomView) - + scrollContentView.layoutChain .edges() .widthToView(scrollView) - + headerBgImgView.layoutChain .edges(excludingEdge: .bottom) .heightToWidth(267/375) - + vipRightsDetailView.layoutChain .top(36) .right() .height(24) - + cornerView.layoutChain .topToBottomOfView(headerBgImgView, offset: -20) .edges(excludingEdge: .top) - + let expenseCollectionViewHeight = (kScreenWidth - 32 - 32) / 3 * (144/110) + 10 expenseCollectionView.layoutChain .topToView(cornerView, offset: -43) .edgesHorzontal() .height(expenseCollectionViewHeight * 1.1) - + vipRightsView.layoutChain .topToBottomOfView(expenseCollectionView, offset: 18) .edgesHorzontal(16) - + vipRightsTitleView.layoutChain .top(14) .centerX() - + groupCountView.layoutChain .topToBottomOfView(vipRightsTitleView, offset: 9) .edgesHorzontal() - + vipRightsCollectionView.layoutChain .topToBottomOfView(groupCountView, offset: 14) .edgesHorzontal() .bottom(14) .height(150) - + agreementLab.layoutChain .topToBottomOfView(vipRightsView, offset: 6) .leftToView(vipRightsView) .rightToView(vipRightsView) - + tipsLab.layoutChain .topToBottomOfView(agreementLab, offset: 4) .leftToView(vipRightsView) .rightToView(vipRightsView) .bottom(10) } - + lazy var navBarView: UIView = { let view = UIView() view.backgroundColor = .white view.alpha = 0 return view }() - + lazy var navTitleLabel: UILabel = { let label = UILabel() label.text = "升级会员" @@ -158,14 +158,14 @@ class VipRechargeView: UIView { 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: "#F5FBFB") @@ -175,43 +175,43 @@ class VipRechargeView: UIView { view.contentInsetAdjustmentBehavior = .never return view }() - + lazy var scrollContentView: UIView = { let view = UIView() view.backgroundColor = .clear return view }() - + lazy var headerBgImgView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "VipRecharge/header_bg") return view }() - + lazy var vipRightsDetailView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#FFFFFF", alpha: 0.7) - + let label = UILabel() label.text = "权益对比" label.font = .systemFont(ofSize: 11, weight: .bold) label.textColor = UIColor(hexStr: "#224E92") - + view.addSubview(label) label.layoutChain .edgesVertical(4) .edgesHorzontal(8) - + return view }() - + lazy var cornerView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#F5FBFB") view.clipsToBounds = false return view }() - + /// 资费 lazy var expenseCollectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() @@ -222,14 +222,14 @@ class VipRechargeView: UIView { layout.sectionInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) layout.minimumLineSpacing = spacing layout.scrollDirection = .horizontal - + let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.backgroundColor = .clear cv.showsHorizontalScrollIndicator = false cv.register(ExpenseCell.self) return cv }() - + /// 会员权益 lazy var vipRightsView: UIView = { let view = UIView() @@ -237,11 +237,11 @@ class VipRechargeView: UIView { view.cornerRadius = 10 return view }() - + lazy var vipRightsTitleView: UIView = { let view = UIView() view.backgroundColor = .clear - + let titleLab = UILabel() titleLab.text = "会员权益" titleLab.font = .systemFont(ofSize: 14, weight: .bold) @@ -251,7 +251,7 @@ class VipRechargeView: UIView { titleLab.layoutChain .edgesVertical() .centerX() - + let leftLine1 = UIView() leftLine1.backgroundColor = UIColor(hexStr: "#B7F34E") leftLine1.cornerRadius = 1 @@ -261,7 +261,7 @@ class VipRechargeView: UIView { .width(2) .height(10) .centerY() - + let leftLine2 = UIView() leftLine2.backgroundColor = UIColor(hexStr: "#B7F34E") leftLine2.cornerRadius = 1 @@ -271,7 +271,7 @@ class VipRechargeView: UIView { .width(2) .edgesVertical() .rightToLeftOfView(titleLab, offset: -10) - + let leftLine3 = UIView() leftLine3.backgroundColor = UIColor(hexStr: "#B7F34E") leftLine3.cornerRadius = 1 @@ -280,7 +280,7 @@ class VipRechargeView: UIView { .leftToRightOfView(titleLab, offset: 10) .width(2) .edgesVertical() - + let leftLine4 = UIView() leftLine4.backgroundColor = UIColor(hexStr: "#B7F34E") leftLine4.cornerRadius = 1 @@ -290,195 +290,195 @@ class VipRechargeView: UIView { .width(2) .height(10) .centerY() - + return view }() - + lazy var groupCountView: UIView = { let view = UIView() view.backgroundColor = .clear - + // 创建圈子 let createCountView = UIView() createCountView.backgroundColor = .clear - + let createIcon = UIImageView(image: UIImage(named: "VipRecharge/create_count")) - + let createBgImg = UIImageView(image: UIImage(named: "VipRecharge/count_bg")) - + let createUnitLab = UILabel() createUnitLab.text = "个" createUnitLab.font = .systemFont(ofSize: 10, weight: .medium) createUnitLab.textColor = UIColor(hexStr: "#1A1A1A") - + let createTitleLab = UILabel() createTitleLab.text = "创建圈子" createTitleLab.font = .systemFont(ofSize: 12, weight: .medium) createTitleLab.textColor = UIColor(hexStr: "#1A1A1A") - + createCountView.addSubview(createIcon) createCountView.addSubview(createBgImg) createCountView.addSubview(createUnitLab) createCountView.addSubview(createTitleLab) createCountView.addSubview(createCountLab) - + createIcon.layoutChain .top() .centerX() .width(34).height(34) - + createBgImg.layoutChain .top(8) .edges(excludingEdge: .top) - + createCountLab.layoutChain .topToBottomOfView(createIcon, offset: 2) .centerX() - + createUnitLab.layoutChain .leftToRightOfView(createCountLab) .bottomToView(createCountLab, offset: 0) - + createTitleLab.layoutChain .centerX() .bottom(4) - + // 分隔线 let separator1 = UIImageView(image: UIImage(named: "VipRecharge/separator")) - + // 圈子人数 let groupCountView = UIView() groupCountView.backgroundColor = .clear - + let groupCountIcon = UIImageView(image: UIImage(named: "VipRecharge/member_count")) - + let groupCountBgImg = UIImageView(image: UIImage(named: "VipRecharge/count_bg")) - + let groupCountUnitLab = UILabel() groupCountUnitLab.text = "个" groupCountUnitLab.font = .systemFont(ofSize: 10, weight: .medium) groupCountUnitLab.textColor = UIColor(hexStr: "#1A1A1A") - + let groupCountTitleLab = UILabel() groupCountTitleLab.text = "圈子人数" groupCountTitleLab.font = .systemFont(ofSize: 12, weight: .medium) groupCountTitleLab.textColor = UIColor(hexStr: "#1A1A1A") - + groupCountView.addSubview(groupCountIcon) groupCountView.addSubview(groupCountBgImg) groupCountView.addSubview(groupCountUnitLab) groupCountView.addSubview(groupCountTitleLab) groupCountView.addSubview(groupCountLab) - + groupCountIcon.layoutChain .top() .centerX() .width(34).height(34) - + groupCountBgImg.layoutChain .top(8) .edges(excludingEdge: .top) - + groupCountLab.layoutChain .topToBottomOfView(groupCountIcon, offset: 2) .centerX() - + groupCountUnitLab.layoutChain .leftToRightOfView(groupCountLab) .bottomToView(groupCountLab, offset: 0) - + groupCountTitleLab.layoutChain .centerX() .bottom(4) - + // 分隔线 let separator2 = UIImageView(image: UIImage(named: "VipRecharge/separator")) - + // 加入圈子 let joinCountView = UIView() joinCountView.backgroundColor = .clear - + let joinCountIcon = UIImageView(image: UIImage(named: "VipRecharge/join_count")) - + let joinCountBgImg = UIImageView(image: UIImage(named: "VipRecharge/count_bg")) - + let joinCountUnitLab = UILabel() joinCountUnitLab.text = "个" joinCountUnitLab.font = .systemFont(ofSize: 10, weight: .medium) joinCountUnitLab.textColor = UIColor(hexStr: "#1A1A1A") - + let joinCountTitleLab = UILabel() joinCountTitleLab.text = "加入圈子" joinCountTitleLab.font = .systemFont(ofSize: 12, weight: .medium) joinCountTitleLab.textColor = UIColor(hexStr: "#1A1A1A") - + joinCountView.addSubview(joinCountIcon) joinCountView.addSubview(joinCountBgImg) joinCountView.addSubview(joinCountUnitLab) joinCountView.addSubview(joinCountTitleLab) joinCountView.addSubview(joinCountLab) - + joinCountIcon.layoutChain .top() .centerX() .width(34).height(34) - + joinCountBgImg.layoutChain .top(8) .edges(excludingEdge: .top) - + joinCountLab.layoutChain .topToBottomOfView(joinCountIcon, offset: 2) .centerX() - + joinCountUnitLab.layoutChain .leftToRightOfView(joinCountLab) .bottomToView(joinCountLab, offset: 0) - + joinCountTitleLab.layoutChain .centerX() .bottom(4) - + view.addSubview(createCountView) view.addSubview(separator1) view.addSubview(groupCountView) view.addSubview(separator2) view.addSubview(joinCountView) - + groupCountView.layoutChain .top() .centerX() .width(69) .height(90) .bottom() - + separator1.layoutChain .rightToLeftOfView(groupCountView, offset: -26) .centerY() .height(85) .width(1) - + createCountView.layoutChain .topToView(groupCountView) .rightToLeftOfView(separator1, offset: -26) .width(69) .height(90) - + separator2.layoutChain .leftToRightOfView(groupCountView, offset: 26) .centerY() .height(85) .width(1) - + joinCountView.layoutChain .topToView(groupCountView) .leftToRightOfView(separator2, offset: 26) .width(69) .height(90) - + return view }() - + lazy var createCountLab: UILabel = { let label = UILabel() label.text = "0" @@ -486,7 +486,7 @@ class VipRechargeView: UIView { label.textColor = UIColor(hexStr: "#FF4F44") return label }() - + lazy var groupCountLab: UILabel = { let label = UILabel() label.text = "0" @@ -494,7 +494,7 @@ class VipRechargeView: UIView { label.textColor = UIColor(hexStr: "#FF4F44") return label }() - + lazy var joinCountLab: UILabel = { let label = UILabel() label.text = "0" @@ -502,7 +502,7 @@ class VipRechargeView: UIView { label.textColor = UIColor(hexStr: "#FF4F44") return label }() - + lazy var vipRightsCollectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() let spacing: CGFloat = 35 @@ -513,20 +513,20 @@ class VipRechargeView: UIView { layout.minimumLineSpacing = 20 layout.minimumInteritemSpacing = spacing layout.scrollDirection = .vertical - + let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.backgroundColor = .clear cv.showsHorizontalScrollIndicator = false cv.register(VipRightsCell.self) return cv }() - + lazy var agreementLab: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 10, weight: .medium) label.textColor = UIColor(hexStr: "#767676") label.isUserInteractionEnabled = true - + let text = "*开通前请阅读《会员服务协议》" let attr = NSMutableAttributedString(string: text) let range = (text as NSString).range(of: "《会员服务协议》") @@ -534,7 +534,7 @@ class VipRechargeView: UIView { label.attributedText = attr return label }() - + lazy var tipsLab: UILabel = { let label = UILabel() label.text = "*根据相关隐私保护的规定,本产品功能需双方下载并授权同意后再使用,请在自己的设备上使用,不得在未经过对方同意和授权的情况下使用,仅限家庭/亲人/朋友/情侣等熟人间使用。" @@ -543,7 +543,7 @@ class VipRechargeView: UIView { label.numberOfLines = 0 return label }() - + lazy var payTypeStackView: UIStackView = { let stack = UIStackView() stack.axis = .horizontal @@ -562,6 +562,7 @@ class VipRechargeView: UIView { let payTypeMap: [(String, String)] = types.compactMap { if $0 == "weixin" { return ("wechat", "微信支付") } if $0 == "alipay" { return ("alipay", "支付宝支付") } + if $0 == "apple" { return ("apple", "苹果支付") } return nil } guard !payTypeMap.isEmpty else { return } @@ -622,30 +623,30 @@ class VipRechargeView: UIView { view.layer.shadowOpacity = 0.06 return view }() - + lazy var payBtnView: UIView = { let view = UIView() view.backgroundColor = .clear - + let bgImg = UIImageView() bgImg.image = UIImage(named: "VipRecharge/pay_bg") view.addSubview(bgImg) bgImg.layoutChain.edges() - + let unlockLab = UILabel() unlockLab.text = "解锁会员" unlockLab.font = .systemFont(ofSize: 16, weight: .heavy) unlockLab.textColor = .white view.addSubview(unlockLab) unlockLab.layoutChain.right(21).centerY() - + return view }() - + lazy var payPriceView: UIView = { let view = UIView() view.backgroundColor = .clear - + let titleLab = UILabel() titleLab.text = "合计" titleLab.font = .systemFont(ofSize: 14, weight: .bold) @@ -654,7 +655,7 @@ class VipRechargeView: UIView { titleLab.layoutChain .left() .bottom() - + let symbolLab = UILabel() symbolLab.text = "¥" symbolLab.font = .systemFont(ofSize: 12, weight: .heavy) @@ -663,21 +664,21 @@ class VipRechargeView: UIView { symbolLab.layoutChain .leftToRightOfView(titleLab) .bottomToView(titleLab) - + view.addSubview(priceLab) priceLab.layoutChain .leftToRightOfView(symbolLab) .bottomToView(titleLab, offset: 3) - + view.addSubview(discountLab) discountLab.layoutChain .leftToRightOfView(priceLab, offset: 5) .bottomToView(titleLab) .right() - + return view }() - + lazy var priceLab: UILabel = { let label = UILabel() label.text = "0" @@ -685,7 +686,7 @@ class VipRechargeView: UIView { label.textColor = UIColor(hexStr: "#FF3B05") return label }() - + /// 数字翻滚动画 — 每位数字独立向上或向下滚动 func animatePrice(to newValue: String) { guard let target = Double(newValue) else { @@ -722,18 +723,18 @@ class VipRechargeView: UIView { label.textColor = UIColor(hexStr: "#1A1A1A") return label }() - + override func layoutSubviews() { super.layoutSubviews() cornerView.setNeedsLayout() cornerView.layoutIfNeeded() cornerView.setCornerRadius(corners: [.topLeft, .topRight], withCornerRadii: CGSize(width: 10, height: 10)) - + vipRightsDetailView.setNeedsLayout() vipRightsDetailView.layoutIfNeeded() vipRightsDetailView.setCornerRadius(corners: [.topLeft, .bottomLeft], withCornerRadii: CGSize(width: 6, height: 6)) } - + override init(frame: CGRect) { super.init(frame: .zero) backgroundColor = .white @@ -789,7 +790,7 @@ final class ExpenseCell: UICollectionViewCell { label.textAlignment = .center return label }() - + lazy var priceLab: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 12, weight: .bold) @@ -797,7 +798,7 @@ final class ExpenseCell: UICollectionViewCell { label.textAlignment = .center return label }() - + lazy var originPriceLab: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 12, weight: .bold) @@ -805,7 +806,7 @@ final class ExpenseCell: UICollectionViewCell { label.textAlignment = .center return label }() - + lazy var tipsLab: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 10, weight: .bold) @@ -839,15 +840,15 @@ final class ExpenseCell: UICollectionViewCell { .centerX() .top(30) .edgesHorzontal(2) - + priceLab.layoutChain .centerY() .edgesHorzontal(2) - + originPriceLab.layoutChain .topToBottomOfView(priceLab, offset: 5) .edgesHorzontal(2) - + tipsLab.layoutChain .edgesHorzontal(2) .bottom(7) @@ -887,7 +888,7 @@ final class ExpenseCell: UICollectionViewCell { animations() } } - + override func layoutSubviews() { super.layoutSubviews() tagBadgeView.setNeedsLayout() @@ -898,12 +899,12 @@ final class ExpenseCell: UICollectionViewCell { // MARK: - ExpenseCell final class VipRightsCell: UICollectionViewCell { - + func configure(_ model: VipRightsModel) { iconView.image = UIImage(named: model.icon) titleLabel.text = model.title } - + private let titleLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 12, weight: .medium) @@ -917,7 +918,7 @@ final class VipRightsCell: UICollectionViewCell { iv.contentMode = .scaleAspectFill return iv }() - + override init(frame: CGRect) { super.init(frame: frame) contentView.addSubview(titleLabel) @@ -927,7 +928,7 @@ final class VipRightsCell: UICollectionViewCell { .top().centerX() .width(40) .height(40) - + titleLabel.layoutChain .edgesHorzontal() .bottom() diff --git a/QuickLocation/Service/OrderService.swift b/QuickLocation/Service/OrderService.swift index 55cc55a..a62a369 100644 --- a/QuickLocation/Service/OrderService.swift +++ b/QuickLocation/Service/OrderService.swift @@ -10,7 +10,7 @@ import Moya struct OrderService { static let disposeBag = DisposeBag() - + /// 充值内容 /// - Parameters: /// - type: 类型 member @@ -20,7 +20,7 @@ struct OrderService { .map(VipExpenseResponse.self) .asObservable() } - + /// 支付参数 /// - Parameters: /// - goodsId: 商品ID @@ -33,4 +33,12 @@ struct OrderService { .map(ResponseModel.self) .asObservable() } + + /// Apple 内购通知 + static func appleNotify(receipt: String, productId: String, transactionId: String) -> Observable { + let api = OrderAPI.appleNotify(receipt: receipt, productId: productId, transactionId: transactionId).multiTarget + return APIProvider.request(token: api) + .map { _ in () } + .asObservable() + } }