diff --git a/ReaderHive.xcodeproj/project.pbxproj b/ReaderHive.xcodeproj/project.pbxproj index ab58e3e..c070ddf 100644 --- a/ReaderHive.xcodeproj/project.pbxproj +++ b/ReaderHive.xcodeproj/project.pbxproj @@ -178,6 +178,41 @@ F3B8593E2EE677740095A9CC /* NRLocalizedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8593D2EE677740095A9CC /* NRLocalizedModel.swift */; }; F3B859402EE6787E0095A9CC /* NRLocalizedManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8593F2EE6787E0095A9CC /* NRLocalizedManager.swift */; }; F3B859422EE678FB0095A9CC /* NRSettingAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859412EE678FB0095A9CC /* NRSettingAPI.swift */; }; + F3B859442EE902BF0095A9CC /* NRStoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859432EE902BF0095A9CC /* NRStoreViewController.swift */; }; + F3B8594C2EE904980095A9CC /* NRPayDateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8594B2EE904980095A9CC /* NRPayDateModel.swift */; }; + F3B8594E2EE905A70095A9CC /* NRStoreAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8594D2EE905A40095A9CC /* NRStoreAPI.swift */; }; + F3B859522EE906A90095A9CC /* JXIAPManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859512EE906A80095A9CC /* JXIAPManager.swift */; }; + F3B859572EE9072C0095A9CC /* NRIapManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859562EE907280095A9CC /* NRIapManager.swift */; }; + F3B859592EE9073B0095A9CC /* NRIAPOrderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859582EE907350095A9CC /* NRIAPOrderModel.swift */; }; + F3B8595B2EE907600095A9CC /* NRWaitRestoreModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8595A2EE907600095A9CC /* NRWaitRestoreModel.swift */; }; + F3B8595D2EE907710095A9CC /* NRIAPVerifyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8595C2EE907710095A9CC /* NRIAPVerifyModel.swift */; }; + F3B8595F2EE910020095A9CC /* NRPayDataRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8595E2EE910020095A9CC /* NRPayDataRequest.swift */; }; + F3B859612EE9126E0095A9CC /* Dictionary+NRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859602EE9126A0095A9CC /* Dictionary+NRAdd.swift */; }; + F3B859632EE91B850095A9CC /* NRStoreCoinsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859622EE91B850095A9CC /* NRStoreCoinsView.swift */; }; + F3B859652EE91BB70095A9CC /* NRStoreCoinsBigCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859642EE91BB70095A9CC /* NRStoreCoinsBigCell.swift */; }; + F3B859672EE91BC50095A9CC /* NRStoreCoinsSmallCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859662EE91BC50095A9CC /* NRStoreCoinsSmallCell.swift */; }; + F3B859692EE91BD70095A9CC /* NRStoreCoinsPackCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859682EE91BD70095A9CC /* NRStoreCoinsPackCell.swift */; }; + F3B8596B2EE91C9F0095A9CC /* NRStoreCoinsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8596A2EE91C9F0095A9CC /* NRStoreCoinsCell.swift */; }; + F3B8596D2EE944DE0095A9CC /* NRStoreVipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8596C2EE944DE0095A9CC /* NRStoreVipView.swift */; }; + F3B8596F2EE9456E0095A9CC /* NRStoreVipCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8596E2EE9456E0095A9CC /* NRStoreVipCell.swift */; }; + F3B859712EE94A1B0095A9CC /* NRFeedbackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859702EE94A1B0095A9CC /* NRFeedbackViewController.swift */; }; + F3B859732EE94A760095A9CC /* NRAppWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859722EE94A760095A9CC /* NRAppWebViewController.swift */; }; + F3B859752EE9515B0095A9CC /* NRMeCoinsPackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859742EE9515B0095A9CC /* NRMeCoinsPackView.swift */; }; + F3B859772EE95B220095A9CC /* NRMeVipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859762EE95B220095A9CC /* NRMeVipView.swift */; }; + F3B859792EE960D20095A9CC /* NRWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859782EE960D20095A9CC /* NRWalletViewController.swift */; }; + F3B8597B2EE961EF0095A9CC /* NRWalletCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8597A2EE961EF0095A9CC /* NRWalletCell.swift */; }; + F3B8597D2EE9627B0095A9CC /* NRWalletHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8597C2EE9627B0095A9CC /* NRWalletHeaderView.swift */; }; + F3B8597F2EE96F810095A9CC /* NRConsumptionRecordsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8597E2EE96F810095A9CC /* NRConsumptionRecordsViewController.swift */; }; + F3B859812EE9716E0095A9CC /* NRBuyRecordsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859802EE9716E0095A9CC /* NRBuyRecordsModel.swift */; }; + F3B859862EE972F70095A9CC /* NRConsumptionRecordsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859842EE972F70095A9CC /* NRConsumptionRecordsCell.swift */; }; + F3B859872EE972F70095A9CC /* NRConsumptionRecordsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3B859852EE972F70095A9CC /* NRConsumptionRecordsCell.xib */; }; + F3B859892EE97E1F0095A9CC /* NRRewardCoinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859882EE97E1F0095A9CC /* NRRewardCoinsViewController.swift */; }; + F3B8598B2EEA51540095A9CC /* NRRewardCoinsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8598A2EEA51540095A9CC /* NRRewardCoinsModel.swift */; }; + F3B8598D2EEA51FE0095A9CC /* NRRewardCoinsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8598C2EEA51FE0095A9CC /* NRRewardCoinsCell.swift */; }; + F3B8598F2EEA5B1C0095A9CC /* NROrderRecordsPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8598E2EEA5B1C0095A9CC /* NROrderRecordsPageViewController.swift */; }; + F3B859912EEA627F0095A9CC /* NROrderRecordsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859902EEA627F0095A9CC /* NROrderRecordsViewController.swift */; }; + F3B859932EEA63CD0095A9CC /* NROrderRecordsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859922EEA63CD0095A9CC /* NROrderRecordsModel.swift */; }; + F3B859952EEA64430095A9CC /* NROrderRecordsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859942EEA64430095A9CC /* NROrderRecordsCell.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -358,6 +393,41 @@ F3B8593D2EE677740095A9CC /* NRLocalizedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRLocalizedModel.swift; sourceTree = ""; }; F3B8593F2EE6787E0095A9CC /* NRLocalizedManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRLocalizedManager.swift; sourceTree = ""; }; F3B859412EE678FB0095A9CC /* NRSettingAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRSettingAPI.swift; sourceTree = ""; }; + F3B859432EE902BF0095A9CC /* NRStoreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreViewController.swift; sourceTree = ""; }; + F3B8594B2EE904980095A9CC /* NRPayDateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRPayDateModel.swift; sourceTree = ""; }; + F3B8594D2EE905A40095A9CC /* NRStoreAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreAPI.swift; sourceTree = ""; }; + F3B859512EE906A80095A9CC /* JXIAPManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JXIAPManager.swift; sourceTree = ""; }; + F3B859562EE907280095A9CC /* NRIapManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRIapManager.swift; sourceTree = ""; }; + F3B859582EE907350095A9CC /* NRIAPOrderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRIAPOrderModel.swift; sourceTree = ""; }; + F3B8595A2EE907600095A9CC /* NRWaitRestoreModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRWaitRestoreModel.swift; sourceTree = ""; }; + F3B8595C2EE907710095A9CC /* NRIAPVerifyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRIAPVerifyModel.swift; sourceTree = ""; }; + F3B8595E2EE910020095A9CC /* NRPayDataRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRPayDataRequest.swift; sourceTree = ""; }; + F3B859602EE9126A0095A9CC /* Dictionary+NRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+NRAdd.swift"; sourceTree = ""; }; + F3B859622EE91B850095A9CC /* NRStoreCoinsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreCoinsView.swift; sourceTree = ""; }; + F3B859642EE91BB70095A9CC /* NRStoreCoinsBigCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreCoinsBigCell.swift; sourceTree = ""; }; + F3B859662EE91BC50095A9CC /* NRStoreCoinsSmallCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreCoinsSmallCell.swift; sourceTree = ""; }; + F3B859682EE91BD70095A9CC /* NRStoreCoinsPackCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreCoinsPackCell.swift; sourceTree = ""; }; + F3B8596A2EE91C9F0095A9CC /* NRStoreCoinsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreCoinsCell.swift; sourceTree = ""; }; + F3B8596C2EE944DE0095A9CC /* NRStoreVipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreVipView.swift; sourceTree = ""; }; + F3B8596E2EE9456E0095A9CC /* NRStoreVipCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreVipCell.swift; sourceTree = ""; }; + F3B859702EE94A1B0095A9CC /* NRFeedbackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRFeedbackViewController.swift; sourceTree = ""; }; + F3B859722EE94A760095A9CC /* NRAppWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRAppWebViewController.swift; sourceTree = ""; }; + F3B859742EE9515B0095A9CC /* NRMeCoinsPackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRMeCoinsPackView.swift; sourceTree = ""; }; + F3B859762EE95B220095A9CC /* NRMeVipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRMeVipView.swift; sourceTree = ""; }; + F3B859782EE960D20095A9CC /* NRWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRWalletViewController.swift; sourceTree = ""; }; + F3B8597A2EE961EF0095A9CC /* NRWalletCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRWalletCell.swift; sourceTree = ""; }; + F3B8597C2EE9627B0095A9CC /* NRWalletHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRWalletHeaderView.swift; sourceTree = ""; }; + F3B8597E2EE96F810095A9CC /* NRConsumptionRecordsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRConsumptionRecordsViewController.swift; sourceTree = ""; }; + F3B859802EE9716E0095A9CC /* NRBuyRecordsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRBuyRecordsModel.swift; sourceTree = ""; }; + F3B859842EE972F70095A9CC /* NRConsumptionRecordsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRConsumptionRecordsCell.swift; sourceTree = ""; }; + F3B859852EE972F70095A9CC /* NRConsumptionRecordsCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NRConsumptionRecordsCell.xib; sourceTree = ""; }; + F3B859882EE97E1F0095A9CC /* NRRewardCoinsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRRewardCoinsViewController.swift; sourceTree = ""; }; + F3B8598A2EEA51540095A9CC /* NRRewardCoinsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRRewardCoinsModel.swift; sourceTree = ""; }; + F3B8598C2EEA51FE0095A9CC /* NRRewardCoinsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRRewardCoinsCell.swift; sourceTree = ""; }; + F3B8598E2EEA5B1C0095A9CC /* NROrderRecordsPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NROrderRecordsPageViewController.swift; sourceTree = ""; }; + F3B859902EEA627F0095A9CC /* NROrderRecordsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NROrderRecordsViewController.swift; sourceTree = ""; }; + F3B859922EEA63CD0095A9CC /* NROrderRecordsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NROrderRecordsModel.swift; sourceTree = ""; }; + F3B859942EEA64430095A9CC /* NROrderRecordsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NROrderRecordsCell.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -375,6 +445,7 @@ 0373D93C2ED578FC0017DCC7 /* API */ = { isa = PBXGroup; children = ( + F3B8594D2EE905A40095A9CC /* NRStoreAPI.swift */, F34991022EE160E50039E939 /* NRUserAPI.swift */, F343492B2EDE72EE00AA7E70 /* NRHomeAPI.swift */, 0373D94A2ED582E10017DCC7 /* NRNovelAPI.swift */, @@ -485,6 +556,7 @@ 039810752ED054090006E317 /* Class */, 03980F8E2ED00ABC0006E317 /* Source */, 039810742ED053F40006E317 /* Libs */, + F3B8594F2EE9068E0095A9CC /* Thirdparty */, ); path = ReaderHive; sourceTree = ""; @@ -559,6 +631,7 @@ 0398106E2ED053630006E317 /* Extension */ = { isa = PBXGroup; children = ( + F3B859602EE9126A0095A9CC /* Dictionary+NRAdd.swift */, F34990B62EDE78680039E939 /* UIScrollView+Refresh.swift */, F34348E62ED7F91500AA7E70 /* NSNumber+NRAdd.swift */, 0373D95B2ED598830017DCC7 /* UIStackView+NRAdd.swift */, @@ -576,6 +649,7 @@ 039810742ED053F40006E317 /* Libs */ = { isa = PBXGroup; children = ( + F3B859552EE907220095A9CC /* IAP */, F3B8593A2EE676B30095A9CC /* LocalizedManager */, F349911D2EE26B020039E939 /* Alert */, F34990EF2EE00F460039E939 /* KeyedArchiver */, @@ -598,6 +672,7 @@ F34348A92ED5B59C00AA7E70 /* Explore */, 0373D93D2ED57A500017DCC7 /* Novel */, F34990C02EDFCD180039E939 /* Me */, + F3B859452EE904390095A9CC /* Store */, ); path = Class; sourceTree = ""; @@ -905,6 +980,8 @@ F34990FE2EE158790039E939 /* NRMeCell.swift */, F34991002EE1593A0039E939 /* NRMeHeaderView.swift */, F3B859322EE66DD10095A9CC /* NRMeCoinsContentView.swift */, + F3B859762EE95B220095A9CC /* NRMeVipView.swift */, + F3B859742EE9515B0095A9CC /* NRMeCoinsPackView.swift */, F3B859342EE66F530095A9CC /* NRMeCoinsView.swift */, F34991082EE169C60039E939 /* NRAboutHeaderView.swift */, F349910A2EE16B520039E939 /* NRAboutCell.swift */, @@ -922,6 +999,7 @@ F34991132EE175E30039E939 /* NRHistoryViewController.swift */, F34991152EE176640039E939 /* NRNovelHistoryViewController.swift */, F3B859362EE6750B0095A9CC /* NRLanguageViewController.swift */, + F3B859702EE94A1B0095A9CC /* NRFeedbackViewController.swift */, ); path = VC; sourceTree = ""; @@ -938,6 +1016,7 @@ isa = PBXGroup; children = ( F349910D2EE1707C0039E939 /* NRWebViewController.swift */, + F3B859722EE94A760095A9CC /* NRAppWebViewController.swift */, F349910F2EE170850039E939 /* NRWebViewController+Script.swift */, F34991112EE170DD0039E939 /* NRWebView.swift */, ); @@ -963,6 +1042,88 @@ path = LocalizedManager; sourceTree = ""; }; + F3B859452EE904390095A9CC /* Store */ = { + isa = PBXGroup; + children = ( + F3B8594A2EE9045C0095A9CC /* VC */, + F3B859482EE9044F0095A9CC /* V */, + F3B859492EE904580095A9CC /* M */, + ); + path = Store; + sourceTree = ""; + }; + F3B859482EE9044F0095A9CC /* V */ = { + isa = PBXGroup; + children = ( + F3B859622EE91B850095A9CC /* NRStoreCoinsView.swift */, + F3B8596C2EE944DE0095A9CC /* NRStoreVipView.swift */, + F3B8596A2EE91C9F0095A9CC /* NRStoreCoinsCell.swift */, + F3B859642EE91BB70095A9CC /* NRStoreCoinsBigCell.swift */, + F3B859662EE91BC50095A9CC /* NRStoreCoinsSmallCell.swift */, + F3B859682EE91BD70095A9CC /* NRStoreCoinsPackCell.swift */, + F3B8596E2EE9456E0095A9CC /* NRStoreVipCell.swift */, + F3B8597A2EE961EF0095A9CC /* NRWalletCell.swift */, + F3B8597C2EE9627B0095A9CC /* NRWalletHeaderView.swift */, + F3B859842EE972F70095A9CC /* NRConsumptionRecordsCell.swift */, + F3B859852EE972F70095A9CC /* NRConsumptionRecordsCell.xib */, + F3B8598C2EEA51FE0095A9CC /* NRRewardCoinsCell.swift */, + F3B859942EEA64430095A9CC /* NROrderRecordsCell.swift */, + ); + path = V; + sourceTree = ""; + }; + F3B859492EE904580095A9CC /* M */ = { + isa = PBXGroup; + children = ( + F3B8594B2EE904980095A9CC /* NRPayDateModel.swift */, + F3B859802EE9716E0095A9CC /* NRBuyRecordsModel.swift */, + F3B8598A2EEA51540095A9CC /* NRRewardCoinsModel.swift */, + F3B859922EEA63CD0095A9CC /* NROrderRecordsModel.swift */, + ); + path = M; + sourceTree = ""; + }; + F3B8594A2EE9045C0095A9CC /* VC */ = { + isa = PBXGroup; + children = ( + F3B859432EE902BF0095A9CC /* NRStoreViewController.swift */, + F3B859782EE960D20095A9CC /* NRWalletViewController.swift */, + F3B8597E2EE96F810095A9CC /* NRConsumptionRecordsViewController.swift */, + F3B859882EE97E1F0095A9CC /* NRRewardCoinsViewController.swift */, + F3B8598E2EEA5B1C0095A9CC /* NROrderRecordsPageViewController.swift */, + F3B859902EEA627F0095A9CC /* NROrderRecordsViewController.swift */, + ); + path = VC; + sourceTree = ""; + }; + F3B8594F2EE9068E0095A9CC /* Thirdparty */ = { + isa = PBXGroup; + children = ( + F3B859502EE906A40095A9CC /* JXIAPManager */, + ); + path = Thirdparty; + sourceTree = ""; + }; + F3B859502EE906A40095A9CC /* JXIAPManager */ = { + isa = PBXGroup; + children = ( + F3B859512EE906A80095A9CC /* JXIAPManager.swift */, + ); + path = JXIAPManager; + sourceTree = ""; + }; + F3B859552EE907220095A9CC /* IAP */ = { + isa = PBXGroup; + children = ( + F3B859562EE907280095A9CC /* NRIapManager.swift */, + F3B8595E2EE910020095A9CC /* NRPayDataRequest.swift */, + F3B859582EE907350095A9CC /* NRIAPOrderModel.swift */, + F3B8595C2EE907710095A9CC /* NRIAPVerifyModel.swift */, + F3B8595A2EE907600095A9CC /* NRWaitRestoreModel.swift */, + ); + path = IAP; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1027,6 +1188,7 @@ files = ( 03980F8A2ED009EB0006E317 /* Assets.xcassets in Resources */, 039810732ED053BE0006E317 /* Localizable.strings in Resources */, + F3B859872EE972F70095A9CC /* NRConsumptionRecordsCell.xib in Resources */, 03980F8C2ED009EB0006E317 /* LaunchScreen.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1094,10 +1256,15 @@ F34348FF2ED85BF200AA7E70 /* NRReadBatteryView.swift in Sources */, F3B8593C2EE677170095A9CC /* NRLanguageModel.swift in Sources */, 039810BA2ED4377E0006E317 /* NRHomeNovelReadWhatCell.swift in Sources */, + F3B8596B2EE91C9F0095A9CC /* NRStoreCoinsCell.swift in Sources */, + F3B859712EE94A1B0095A9CC /* NRFeedbackViewController.swift in Sources */, F34348C72ED6CCBC00AA7E70 /* NRExploreNovelContentListViewController.swift in Sources */, 039810B82ED431780006E317 /* NRHomeNovelReadWhatView.swift in Sources */, F34349142EDA9AE900AA7E70 /* NRNovelReadSettingView.swift in Sources */, + F3B859632EE91B850095A9CC /* NRStoreCoinsView.swift in Sources */, F34348BD2ED68F2B00AA7E70 /* NRExploreNovelViewModel.swift in Sources */, + F3B8595F2EE910020095A9CC /* NRPayDataRequest.swift in Sources */, + F3B859692EE91BD70095A9CC /* NRStoreCoinsPackCell.swift in Sources */, F34991102EE1708C0039E939 /* NRWebViewController+Script.swift in Sources */, F34348B12ED5B9A400AA7E70 /* NRExploreNovelViewController.swift in Sources */, F34348BF2ED691C100AA7E70 /* NRExploreNovelGenresViewController.swift in Sources */, @@ -1112,26 +1279,33 @@ F34348C32ED6A20700AA7E70 /* NRExploreNovelContentViewController.swift in Sources */, F34991292EE285660039E939 /* NRShowRecommendPop.swift in Sources */, 0373D9642ED5ABBC0017DCC7 /* NREmpty.swift in Sources */, + F3B859572EE9072C0095A9CC /* NRIapManager.swift in Sources */, 039810B62ED42D840006E317 /* NRHomeNovelNewArrivalsCell.swift in Sources */, 0373D95A2ED593D50017DCC7 /* NRSearchRecordCell.swift in Sources */, 0373D9582ED5935D0017DCC7 /* NRSearchRecordView.swift in Sources */, F343490C2ED9751800AA7E70 /* NRReadChapterCatalogModel.swift in Sources */, + F3B859862EE972F70095A9CC /* NRConsumptionRecordsCell.swift in Sources */, F34990C72EDFCE500039E939 /* NRNovelReadGradeView.swift in Sources */, 039810B42ED428F20006E317 /* NRHomeNovelNewArrivalsView.swift in Sources */, F34991012EE1593A0039E939 /* NRMeHeaderView.swift in Sources */, + F3B8598F2EEA5B1C0095A9CC /* NROrderRecordsPageViewController.swift in Sources */, F34991052EE165EA0039E939 /* NRMeItem.swift in Sources */, 039810CC2ED477CD0006E317 /* UIView+NRAdd.swift in Sources */, F3B859312EE66B950095A9CC /* NRDetailRechargeView.swift in Sources */, F34348B92ED5C7E400AA7E70 /* NRExploreNovelMenuCell.swift in Sources */, F343490A2ED96EE600AA7E70 /* NRNovelReadView.swift in Sources */, 039810B22ED3F5FB0006E317 /* NRGradientView.swift in Sources */, + F3B8596D2EE944DE0095A9CC /* NRStoreVipView.swift in Sources */, 039810852ED056D70006E317 /* UserDefaults+NRAdd.swift in Sources */, + F3B8595B2EE907600095A9CC /* NRWaitRestoreModel.swift in Sources */, 039810CA2ED469D50006E317 /* NRWaterfallFlowLayout.swift in Sources */, F34348F72ED84B0D00AA7E70 /* NRNovelReaderViewController+Page.swift in Sources */, 039810902ED060EF0006E317 /* NRViewController.swift in Sources */, 039810702ED053910006E317 /* String+NRAdd.swift in Sources */, 039810932ED062CE0006E317 /* NRHomeViewController.swift in Sources */, + F3B859652EE91BB70095A9CC /* NRStoreCoinsBigCell.swift in Sources */, 039810BC2ED43C8E0006E317 /* NRReadWhatViewTransformer.swift in Sources */, + F3B859932EEA63CD0095A9CC /* NROrderRecordsModel.swift in Sources */, 039810D02ED54D370006E317 /* NRHomeCategoryTagView.swift in Sources */, F34348E32ED70D2F00AA7E70 /* NRNovelDetailHeaderView+Data.swift in Sources */, 0398107F2ED055D10006E317 /* NRLoginManager.swift in Sources */, @@ -1148,6 +1322,7 @@ 0373D9472ED57F3F0017DCC7 /* UINavigationBar+NRAdd.swift in Sources */, 0373D94D2ED583A80017DCC7 /* NRNovelModel.swift in Sources */, F34348AF2ED5B85300AA7E70 /* NRExploreViewController.swift in Sources */, + F3B859732EE94A760095A9CC /* NRAppWebViewController.swift in Sources */, 0373D9602ED59DA10017DCC7 /* NRSearchResultCell.swift in Sources */, 039810952ED066710006E317 /* UIScreen+NRAdd.swift in Sources */, 039810662ED04F940006E317 /* NRUrlPath.swift in Sources */, @@ -1155,16 +1330,20 @@ F34991232EE26EAC0039E939 /* NRAlert.swift in Sources */, F34348B72ED5C75800AA7E70 /* NRTableViewCell.swift in Sources */, F34349102ED9A77A00AA7E70 /* NRPanModalContentView.swift in Sources */, + F3B859892EE97E1F0095A9CC /* NRRewardCoinsViewController.swift in Sources */, 039810CE2ED47A130006E317 /* CGMutablePath+NRRoundedCorner.swift in Sources */, F34348E72ED7F91C00AA7E70 /* NSNumber+NRAdd.swift in Sources */, + F3B8597F2EE96F810095A9CC /* NRConsumptionRecordsViewController.swift in Sources */, F34349122EDA84F100AA7E70 /* NRProgressView.swift in Sources */, F349910B2EE16B520039E939 /* NRAboutCell.swift in Sources */, + F3B859592EE9073B0095A9CC /* NRIAPOrderModel.swift in Sources */, 039810A22ED070400006E317 /* NRHomeNovelHeaderView.swift in Sources */, F34349082ED945DA00AA7E70 /* NRCoreText.swift in Sources */, F349911F2EE26C350039E939 /* NRAlertWindowManager.swift in Sources */, F34991072EE167E80039E939 /* NRAboutViewController.swift in Sources */, F34348ED2ED82B6300AA7E70 /* NRNovelReadSetManager.swift in Sources */, F34990FF2EE158790039E939 /* NRMeCell.swift in Sources */, + F3B859752EE9515B0095A9CC /* NRMeCoinsPackView.swift in Sources */, F34348DD2ED6F9F900AA7E70 /* NRNovelDetailMoreLikeCell.swift in Sources */, 039810AC2ED3EF640006E317 /* NRHomeNovelHeaderContentView.swift in Sources */, 0398106D2ED053000006E317 /* NRDefine.swift in Sources */, @@ -1173,16 +1352,21 @@ F343492C2EDE72F300AA7E70 /* NRHomeAPI.swift in Sources */, F3B859372EE6750B0095A9CC /* NRLanguageViewController.swift in Sources */, F34348E12ED70A2700AA7E70 /* NRNovelDetailHeaderView+NovelCoverInfo.swift in Sources */, + F3B859522EE906A90095A9CC /* JXIAPManager.swift in Sources */, F34349012ED93A9B00AA7E70 /* NRReadChapterModel.swift in Sources */, F34348DF2ED7049E00AA7E70 /* NRNovelDetailHeaderView.swift in Sources */, F34348BB2ED5CD8100AA7E70 /* NRExploreNovelMenuItem.swift in Sources */, 0398109B2ED0692A0006E317 /* NRImageView.swift in Sources */, 0398108A2ED0582F0006E317 /* NRDeviceId.swift in Sources */, + F3B859812EE9716E0095A9CC /* NRBuyRecordsModel.swift in Sources */, + F3B859952EEA64430095A9CC /* NROrderRecordsCell.swift in Sources */, 0373D9542ED58AF00017DCC7 /* NRSearchInputView.swift in Sources */, F34348EB2ED82B4100AA7E70 /* NRNovelReadSet.swift in Sources */, 039810682ED050390006E317 /* NRResponseCryptor.swift in Sources */, 039810C62ED45AE30006E317 /* NRHomeNovelHotTagCell.swift in Sources */, + F3B859912EEA627F0095A9CC /* NROrderRecordsViewController.swift in Sources */, F34348D92ED6F01900AA7E70 /* NRNovelDetailBottomView.swift in Sources */, + F3B8597B2EE961EF0095A9CC /* NRWalletCell.swift in Sources */, F34348CB2ED6DADA00AA7E70 /* NRNovelGenresViewController.swift in Sources */, F3B859422EE678FB0095A9CC /* NRSettingAPI.swift in Sources */, 0373D9522ED58A950017DCC7 /* NRSearchViewModel.swift in Sources */, @@ -1200,14 +1384,18 @@ 0373D9562ED5933F0017DCC7 /* NRSearchHomeView.swift in Sources */, F3B859352EE66F530095A9CC /* NRMeCoinsView.swift in Sources */, 0398108C2ED0584F0006E317 /* NRKeychain.swift in Sources */, + F3B859792EE960D20095A9CC /* NRWalletViewController.swift in Sources */, 039810A02ED06B7C0006E317 /* NRHomeNovelViewController.swift in Sources */, F34990FB2EE121490039E939 /* NRLabel.swift in Sources */, F3B859332EE66DD10095A9CC /* NRMeCoinsContentView.swift in Sources */, F34348F22ED8388F00AA7E70 /* NRNovelReadTopView.swift in Sources */, + F3B859612EE9126E0095A9CC /* Dictionary+NRAdd.swift in Sources */, F34349182EDAA02900AA7E70 /* NRNovelReadSettingItemView.swift in Sources */, 039810812ED056090006E317 /* NRLoginToken.swift in Sources */, + F3B859442EE902BF0095A9CC /* NRStoreViewController.swift in Sources */, F343491A2EDAC2E500AA7E70 /* NRReadSettingThemeView.swift in Sources */, 0373D9402ED57B1C0017DCC7 /* NRNovelDetailViewController.swift in Sources */, + F3B8598B2EEA51540095A9CC /* NRRewardCoinsModel.swift in Sources */, 039810A62ED072820006E317 /* NRCollectionView.swift in Sources */, 039810602ED04D950006E317 /* NRNetworkModel.swift in Sources */, F34348C92ED6CDD900AA7E70 /* NRExploreNovelContentListCell.swift in Sources */, @@ -1221,23 +1409,30 @@ 039810A82ED3E7DB0006E317 /* NRHomeNovelListCell.swift in Sources */, F34991212EE26C660039E939 /* NRBaseAlert.swift in Sources */, F34990FD2EE124CF0039E939 /* NRNovelReadStarGradeView.swift in Sources */, + F3B8594C2EE904980095A9CC /* NRPayDateModel.swift in Sources */, F34991182EE1780A0039E939 /* NRNovelHistoryCell.swift in Sources */, F34990F12EE00F5A0039E939 /* NRKeyedArchiver.swift in Sources */, F34349162EDA9FC700AA7E70 /* NRReadSettingBrightnessView.swift in Sources */, + F3B8598D2EEA51FE0095A9CC /* NRRewardCoinsCell.swift in Sources */, F34990F52EE0346B0039E939 /* NRNovelReadBaseViewController.swift in Sources */, 039810642ED04F480006E317 /* NRTargetType.swift in Sources */, + F3B8595D2EE907710095A9CC /* NRIAPVerifyModel.swift in Sources */, F343491E2EDAD0AA00AA7E70 /* NRReadTheme.swift in Sources */, + F3B8597D2EE9627B0095A9CC /* NRWalletHeaderView.swift in Sources */, 039810872ED057260006E317 /* NRUserDefaultsKey.swift in Sources */, F34349222EDD227A00AA7E70 /* NRReadSettingSpacingView.swift in Sources */, F34990F32EE02FD60039E939 /* NRNovelReadFinishViewController.swift in Sources */, F34348F92ED855AA00AA7E70 /* NRNovelReadContentViewController.swift in Sources */, 039810A42ED072380006E317 /* NRHomeNovelListViewController.swift in Sources */, 039810BE2ED44C210006E317 /* NRHomeNovelHotGridView.swift in Sources */, + F3B859672EE91BC50095A9CC /* NRStoreCoinsSmallCell.swift in Sources */, F3B859392EE676610095A9CC /* NRLanguageCell.swift in Sources */, 0373D95E2ED59C430017DCC7 /* NRSearchResultView.swift in Sources */, F34991142EE175E30039E939 /* NRHistoryViewController.swift in Sources */, F3B8593E2EE677740095A9CC /* NRLocalizedModel.swift in Sources */, 039810AA2ED3EC2E0006E317 /* NRScrollView.swift in Sources */, + F3B859772EE95B220095A9CC /* NRMeVipView.swift in Sources */, + F3B8594E2EE905A70095A9CC /* NRStoreAPI.swift in Sources */, 0373D94F2ED58A1E0017DCC7 /* NRSearchViewController.swift in Sources */, 0398108E2ED060020006E317 /* UIFont+NRAdd.swift in Sources */, F34348D32ED6E0F400AA7E70 /* NRMyListViewController.swift in Sources */, @@ -1247,6 +1442,7 @@ F34348D72ED6E7C600AA7E70 /* NRMyListNovelCell.swift in Sources */, F34349052ED9442300AA7E70 /* NRReadPageModel.swift in Sources */, F34990F92EE118FC0039E939 /* NRNovelReadFinishHeaderView.swift in Sources */, + F3B8596F2EE9456E0095A9CC /* NRStoreVipCell.swift in Sources */, F34990C52EDFCD4A0039E939 /* NRMeViewController.swift in Sources */, F349911C2EE190590039E939 /* NRNovelDetailCatalogViewController.swift in Sources */, F34348F02ED8381E00AA7E70 /* NRNovelReadViewModel.swift in Sources */, @@ -1282,13 +1478,15 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 8NNUR9HPV3; + DEVELOPMENT_TEAM = ""; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ReaderHive/Source/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ReaderHive; + INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = ""; @@ -1300,8 +1498,9 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.hbqinjiu.ReaderHive; + PRODUCT_BUNDLE_IDENTIFIER = com.lssj.ReaderHive; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -1320,12 +1519,16 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 8NNUR9HPV3; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 9JR2Y32ZU3; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ReaderHive/Source/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ReaderHive; + INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = ""; @@ -1337,8 +1540,10 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.hbqinjiu.ReaderHive; + PRODUCT_BUNDLE_IDENTIFIER = com.lssj.ReaderHive; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = readerdev; STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; diff --git a/ReaderHive/Base/Define/NRDefine.swift b/ReaderHive/Base/Define/NRDefine.swift index eeab36b..afb3c03 100644 --- a/ReaderHive/Base/Define/NRDefine.swift +++ b/ReaderHive/Base/Define/NRDefine.swift @@ -6,6 +6,11 @@ // import UIKit + +///当前系统版本号 +let kNROsVersion: String = UIDevice.current.systemVersion +let kNRAPPBundleIdentifier: String = (Bundle.main.infoDictionary!["CFBundleIdentifier"] as? String) ?? "0" + ///app版本号 let kNRAPPVersion: String = (Bundle.main.infoDictionary!["CFBundleShortVersionString"] as? String) ?? "0" let kNRAPPBundleVersion: String = (Bundle.main.infoDictionary!["CFBundleVersion"] as? String) ?? "0" diff --git a/ReaderHive/Base/Define/NRUserDefaultsKey.swift b/ReaderHive/Base/Define/NRUserDefaultsKey.swift index 119fb72..66cbc14 100644 --- a/ReaderHive/Base/Define/NRUserDefaultsKey.swift +++ b/ReaderHive/Base/Define/NRUserDefaultsKey.swift @@ -11,3 +11,5 @@ let kNRLoginTokenDefaultsKey = "kNRLoginTokenDefaultsKey" let kNRUserInfoDefaultsKey = "kNRUserInfoDefaultsKey" ///阅读设置 let kNRNovelReadSetDefaultsKey = "kNRNovelReadSetDefaultsKey" + +let kNRWaitRestoreIAPDefaultsKey = "kNRWaitRestoreIAPDefaultsKey" diff --git a/ReaderHive/Base/Extension/Dictionary+NRAdd.swift b/ReaderHive/Base/Extension/Dictionary+NRAdd.swift new file mode 100644 index 0000000..6f0451c --- /dev/null +++ b/ReaderHive/Base/Extension/Dictionary+NRAdd.swift @@ -0,0 +1,23 @@ +// +// Dictionary+NRAdd.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit + +extension Dictionary { + + func toJsonString() -> String? { + do { + let data = try JSONSerialization.data(withJSONObject: self) + let jsonStr = String(data: data, encoding: .utf8) + return jsonStr + } catch { + + } + return nil + } + +} diff --git a/ReaderHive/Base/Networking/API/NRStoreAPI.swift b/ReaderHive/Base/Networking/API/NRStoreAPI.swift new file mode 100644 index 0000000..facac2b --- /dev/null +++ b/ReaderHive/Base/Networking/API/NRStoreAPI.swift @@ -0,0 +1,130 @@ +// +// NRStoreAPI.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SmartCodable +import Alamofire + + +struct NRStoreAPI { + + enum BuyType: String, SmartCaseDefaultable { + case coins = "coins" + case subVip = "sub_vip" + case subCoins = "sub_coins" + } + + ///获取支付模版 + static func requestPayTemplate(isLoding: Bool = false, isToast: Bool = true, completer: ((_ model: NRPayDateModel?) -> Void)?) { + + var param = NRNetwork.Parameters(path: "/paySettingsV4") + param.method = .get + param.isToast = isToast + param.isLoding = isLoding + param.parameters = [ + "discount" : "1", + "purchases_token" : JXIAPManager.manager.getAppStoreReceipt() ?? "", + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + if response.isSuccess { + completer?(response.data) + } else { + completer?(nil) + } + } + } + + + ///创建内购订单 + static func requestCreateOrder(payId: String, shortPlayId: String, videoId: String, isDiscount: Bool = false, identifierDiscount: String? = nil, completer: ((_ orderModel: NRIAPOrderModel?) -> Void)?) { + var param = NRNetwork.Parameters(path: "/createOrder") + param.isToast = false + param.parameters = [ + "payment_channel" : "apple", + "short_play_id" : shortPlayId, + "video_id" : videoId, + "pay_setting_id" : payId, + "is_discount" : isDiscount ? 1 : 0, + "product_discount" : identifierDiscount ?? "", + ] + + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + guard let data = response.data else { + NRToast.show(text: "network_error_2".localized) + completer?(nil) + return + } + + if let message = data.message, message.count > 0 { + if response.data?.code == 30007 { + NRToast.show(text: "pay_error_1".localized) + } else { + NRToast.show(text: message) + } + completer?(nil) + } else { + completer?(data) + } + } + } + + ///校验内购 + static func requestVerifyOrder(parameters: [String : Any], completer: ((_ response: NRNetwork.Response) -> Void)?) { + var param = NRNetwork.Parameters(path: "/applePaid") + param.parameters = parameters + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + completer?(response) + } + } + + + static func requestBuyRecords(page: Int, completer: ((_ listModel: NRNetwork.List?) -> Void)?) { + var param = NRNetwork.Parameters(path: "/getCustomerBuyRecords") + param.method = .get + param.parameters = [ + "page_size" : 20, + "current_page" : page, + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + completer?(response.data) + } + } + + static func requestRechargeRecord(page: Int, buyType: BuyType, completer: ((_ listModel: NRNetwork.List?) -> Void)?) { + + var param = NRNetwork.Parameters(path: "/getCustomerOrder") + param.method = .get + param.parameters = [ + "page_size" : 20, + "current_page" : page, + "buy_type" : buyType.rawValue + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + completer?(response.data) + } + } + + static func reuqestSendCoinRecord(page: Int, completer: ((_ listModel: NRNetwork.List?) -> Void)?) { + + var param = NRNetwork.Parameters(path: "/sendCoinList") + param.method = .post + param.parameters = [ + "page_size" : 20, + "current_page" : page, + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + completer?(response.data) + } + } + +} diff --git a/ReaderHive/Base/Networking/NRTargetType.swift b/ReaderHive/Base/Networking/NRTargetType.swift index 92f827d..6c78b4f 100644 --- a/ReaderHive/Base/Networking/NRTargetType.swift +++ b/ReaderHive/Base/Networking/NRTargetType.swift @@ -57,7 +57,7 @@ extension NRTargetType: TargetType { "model" : UIDevice.current.machineModelName ?? "", "authorization" : NRLoginManager.manager.token?.token ?? "", "device-gaid" : UIDevice.current.identifierForVendor?.uuidString ?? "", - "product-prefix" : "ReaderHive" + "product-prefix" : NRIapManager.IAPPrefix ] #if DEBUG dic["security"] = "false" diff --git a/ReaderHive/Base/Networking/NRUrlPath.swift b/ReaderHive/Base/Networking/NRUrlPath.swift index ee34441..f14fa7d 100644 --- a/ReaderHive/Base/Networking/NRUrlPath.swift +++ b/ReaderHive/Base/Networking/NRUrlPath.swift @@ -11,6 +11,7 @@ let NRBaseURL = "https://api-readerhive.readerhive.net" let NRWebBaseURL = "https://www.readerhive.net" +let NRCampaignWebURL = "https://campaign.readerhive.com" @@ -18,3 +19,15 @@ let NRWebBaseURL = "https://www.readerhive.net" let kNRUserAgreementWebUrl = NRWebBaseURL + "/user_policy" ///隐私协议 let kNRPrivacyPolicyWebUrl = NRWebBaseURL + "/private" + + + +///反馈首页 +let kNRFeedBackHomeWebUrl = NRCampaignWebURL + "/pages/leave/index" +///反馈列表 +let kNRFeedBackListWebUrl = NRCampaignWebURL + "/pages/leave/list" +///反馈详情 +let kNRFeedBackDetailWebUrl = NRCampaignWebURL + "/pages/leave/detail" + +///注销账号 +let kNRLogoutWebUrl = NRCampaignWebURL + "/pages/setting/logout" diff --git a/ReaderHive/Base/View/NRTableViewCell.swift b/ReaderHive/Base/View/NRTableViewCell.swift index afbf0da..e554091 100644 --- a/ReaderHive/Base/View/NRTableViewCell.swift +++ b/ReaderHive/Base/View/NRTableViewCell.swift @@ -16,15 +16,12 @@ class NRTableViewCell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - - self.layer.rasterizationScale = UIScreen.main.scale - self.layer.shouldRasterize = true - self.selectionStyle = .none - self.backgroundColor = .clear + _init() } required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") + super.init(coder: coder) + _init() } override func awakeFromNib() { @@ -37,6 +34,13 @@ class NRTableViewCell: UITableViewCell { // Configure the view for the selected state } + + private func _init() { + self.layer.rasterizationScale = UIScreen.main.scale + self.layer.shouldRasterize = true + self.selectionStyle = .none + self.backgroundColor = .clear + } } diff --git a/ReaderHive/Base/WebView/NRAppWebViewController.swift b/ReaderHive/Base/WebView/NRAppWebViewController.swift new file mode 100644 index 0000000..a31c573 --- /dev/null +++ b/ReaderHive/Base/WebView/NRAppWebViewController.swift @@ -0,0 +1,78 @@ +// +// NRAppWebViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +internal import WebKit + +class NRAppWebViewController: NRWebViewController { + + var id: String? + + private var receiveDataCount = 0 + + var theme: String? = "theme_3" + + override func viewDidLoad() { + super.viewDidLoad() + + if webUrl == kNRFeedBackListWebUrl { + self.title = "Feedback History".localized + } else if webUrl == kNRFeedBackHomeWebUrl { + self.title = "Feedback".localized + } else if webUrl == kNRFeedBackDetailWebUrl { + self.title = "Feedback Details".localized + } else if webUrl == kNRLogoutWebUrl { + self.title = "Account Deletion".localized + } + + } + + override func nr_webViewDidFinishLoad(_ webView: NRWebView) { + super.nr_webViewDidFinishLoad(webView) + receiveDataCount = 0 + receiveDataFromNative() + } + +} + +extension NRAppWebViewController { + + func receiveDataFromNative() { + receiveDataCount += 1 + if receiveDataCount > 10 { return } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + guard let self = self else { return } + var dic = [ + "token" : NRLoginManager.manager.token?.token ?? "", + "time_zone" : NRTargetType.timeZone(), + "lang" : NRLocalizedManager.shared.currentLocalizedKey, + "type" : "ios", + "device-id" : NRDeviceId.shared.id + ] + + if let theme = theme { + dic["theme"] = theme + } + + if let id = id { + dic["id"] = id + } + + if let json = dic.toJsonString() { + let js = "receiveDataFromNative(\(json))" + self.webView.evaluateJavaScript(js) { [weak self] _, error in + guard let self = self else { return } + if error != nil { + self.receiveDataFromNative() + } + } + } + } + + } +} diff --git a/ReaderHive/Base/WebView/NRWebView.swift b/ReaderHive/Base/WebView/NRWebView.swift index f029ef0..516e34c 100644 --- a/ReaderHive/Base/WebView/NRWebView.swift +++ b/ReaderHive/Base/WebView/NRWebView.swift @@ -6,7 +6,7 @@ // import UIKit -@preconcurrency import WebKit +internal import WebKit import YYText //MARK:-------------- VPWebViewDelegate -------------- diff --git a/ReaderHive/Base/WebView/NRWebViewController+Script.swift b/ReaderHive/Base/WebView/NRWebViewController+Script.swift index 9a56e80..a88686c 100644 --- a/ReaderHive/Base/WebView/NRWebViewController+Script.swift +++ b/ReaderHive/Base/WebView/NRWebViewController+Script.swift @@ -6,7 +6,7 @@ // import UIKit -import WebKit +internal import WebKit ///APP交互 let kNRWebMessageAPP = "js2app" diff --git a/ReaderHive/Base/WebView/NRWebViewController.swift b/ReaderHive/Base/WebView/NRWebViewController.swift index 41d3f22..ae4c766 100644 --- a/ReaderHive/Base/WebView/NRWebViewController.swift +++ b/ReaderHive/Base/WebView/NRWebViewController.swift @@ -6,7 +6,7 @@ // import UIKit -import WebKit +internal import WebKit import SnapKit class NRWebViewController: NRViewController { diff --git a/ReaderHive/Class/Me/M/NRMeItem.swift b/ReaderHive/Class/Me/M/NRMeItem.swift index 752c961..73a6446 100644 --- a/ReaderHive/Class/Me/M/NRMeItem.swift +++ b/ReaderHive/Class/Me/M/NRMeItem.swift @@ -15,6 +15,10 @@ struct NRMeItem { case about case settings case web + case feedback + case consumptionRecords + case purchaseRecords + case rewardCoins } var type: ItemType diff --git a/ReaderHive/Class/Me/V/NRMeCoinsContentView.swift b/ReaderHive/Class/Me/V/NRMeCoinsContentView.swift index 45b1d93..e9769e1 100644 --- a/ReaderHive/Class/Me/V/NRMeCoinsContentView.swift +++ b/ReaderHive/Class/Me/V/NRMeCoinsContentView.swift @@ -7,6 +7,7 @@ import UIKit import SnapKit +import YYCategories class NRMeCoinsContentView: UIView { @@ -24,12 +25,22 @@ class NRMeCoinsContentView: UIView { private lazy var coinsView: NRMeCoinsView = { let view = NRMeCoinsView() view.title = "Coins".localized + view.addGestureRecognizer(UITapGestureRecognizer(actionBlock: { [weak self] _ in + guard let self = self else { return } + let vc = NRWalletViewController() + self.viewController?.navigationController?.pushViewController(vc, animated: true) + })) return view }() private lazy var sendCoinsView: NRMeCoinsView = { let view = NRMeCoinsView() view.title = "Bonus".localized + view.addGestureRecognizer(UITapGestureRecognizer(actionBlock: { [weak self] _ in + guard let self = self else { return } + let vc = NRWalletViewController() + self.viewController?.navigationController?.pushViewController(vc, animated: true) + })) return view }() @@ -40,7 +51,11 @@ class NRMeCoinsContentView: UIView { }() private lazy var topUpButton: UIButton = { - let button = NRGradientButton(type: .custom) + let button = NRGradientButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + let vc = NRStoreViewController() + self.viewController?.navigationController?.pushViewController(vc, animated: true) + })) button.colors = [UIColor.F_3912_F.cgColor, UIColor.FF_4_A_4_A.cgColor, UIColor.FA_9_B_1_F.cgColor] button.startPoint = .init(x: 0, y: 0.5) button.endPoint = .init(x: 1, y: 0.5) diff --git a/ReaderHive/Class/Me/V/NRMeCoinsPackView.swift b/ReaderHive/Class/Me/V/NRMeCoinsPackView.swift new file mode 100644 index 0000000..7a05927 --- /dev/null +++ b/ReaderHive/Class/Me/V/NRMeCoinsPackView.swift @@ -0,0 +1,95 @@ +// +// NRMeCoinsPackView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SnapKit +import YYCategories + +class NRMeCoinsPackView: UIView { + + + private lazy var bgView: UIImageView = { + let view = UIImageView(image: UIImage(named: "me_pack_bg_image")) + view.layer.cornerRadius = 30 + view.layer.masksToBounds = true + view.layer.borderWidth = 1 + view.layer.borderColor = UIColor.FFEFD_4.cgColor + return view + }() + + private lazy var iconImageView = UIImageView(image: UIImage(named: "gift_icon_01")) + + private lazy var titleLabel: UILabel = { + let label = NRLabel() + label.font = .font(ofSize: 14, weight: .semibold) + label.textColors = [UIColor.F_3912_F.cgColor, UIColor.FF_4_A_4_A.cgColor, UIColor.FA_9_B_1_F.cgColor] + label.textStartPoint = .init(x: 0, y: 0.5) + label.textEndPoint = .init(x: 1, y: 0.5) + label.text = "me_coins_pack_title".localized + return label + }() + + private lazy var subtitleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular).withItalic() + label.textColor = .F_9710_D + label.text = "me_coins_pack_subtitle".localized + return label + }() + + private lazy var indicatorImageView = UIImageView(image: UIImage(named: "arrow_right_icon_07")) + + override init(frame: CGRect) { + super.init(frame: frame) + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRMeCoinsPackView { + + private func nr_setupUI() { + addSubview(bgView) + addSubview(iconImageView) + addSubview(titleLabel) + addSubview(subtitleLabel) + addSubview(indicatorImageView) + + bgView.snp.makeConstraints { make in + make.edges.equalToSuperview() + make.height.equalTo(60) + } + + iconImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(24) + } + + titleLabel.snp.makeConstraints { make in + make.left.equalTo(iconImageView.snp.right).offset(8) + make.top.equalToSuperview().offset(12) + make.right.lessThanOrEqualToSuperview().offset(-48) + } + + subtitleLabel.snp.makeConstraints { make in + make.left.equalTo(titleLabel) + make.bottom.equalToSuperview().offset(-12) + make.right.lessThanOrEqualToSuperview().offset(-48) + } + + indicatorImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-16) + } + } + +} diff --git a/ReaderHive/Class/Me/V/NRMeHeaderView.swift b/ReaderHive/Class/Me/V/NRMeHeaderView.swift index 8f97b7b..652446f 100644 --- a/ReaderHive/Class/Me/V/NRMeHeaderView.swift +++ b/ReaderHive/Class/Me/V/NRMeHeaderView.swift @@ -45,7 +45,6 @@ class NRMeHeaderView: UITableViewHeaderFooterView { private lazy var copyButton: UIButton = { let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in guard let self = self else { return } - UIPasteboard.general.string = NRLoginManager.manager.userInfo?.customer_id NRToast.show(text: "Success") })) @@ -65,6 +64,16 @@ class NRMeHeaderView: UITableViewHeaderFooterView { return view }() + private lazy var coinsPackView: NRMeCoinsPackView = { + let view = NRMeCoinsPackView() + return view + }() + + private lazy var vipView: NRMeVipView = { + let view = NRMeVipView() + return view + }() + deinit { NotificationCenter.default.removeObserver(self) } @@ -72,12 +81,13 @@ class NRMeHeaderView: UITableViewHeaderFooterView { override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) NotificationCenter.default.addObserver(self, selector: #selector(userInfoUpdateNotification), name: NRLoginManager.userInfoUpdateNotification, object: nil) - nr_setupUI() userInfoUpdateNotification() stackView.addArrangedSubview(coinsView) + stackView.addArrangedSubview(vipView) + stackView.addArrangedSubview(coinsPackView) } @@ -105,13 +115,13 @@ extension NRMeHeaderView { contentView.addSubview(idBgView) idBgView.addSubview(idLabel) idBgView.addSubview(copyButton) -// contentView.addSubview(stackView) + contentView.addSubview(stackView) avatarImageView.snp.makeConstraints { make in make.left.equalToSuperview() make.top.equalToSuperview().offset(48) make.width.height.equalTo(60) - make.bottom.equalToSuperview().offset(-16) +// make.bottom.equalToSuperview().offset(-16) } nickLabel.snp.makeConstraints { make in @@ -136,11 +146,11 @@ extension NRMeHeaderView { make.left.equalTo(idLabel.snp.right).offset(8) } -// stackView.snp.makeConstraints { make in -// make.left.right.equalToSuperview() -// make.top.equalTo(avatarImageView.snp.bottom).offset(16) -// make.bottom.equalToSuperview().offset(-16) -// } + stackView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalTo(avatarImageView.snp.bottom).offset(16) + make.bottom.equalToSuperview().offset(-16) + } } } diff --git a/ReaderHive/Class/Me/V/NRMeVipView.swift b/ReaderHive/Class/Me/V/NRMeVipView.swift new file mode 100644 index 0000000..2957d04 --- /dev/null +++ b/ReaderHive/Class/Me/V/NRMeVipView.swift @@ -0,0 +1,92 @@ +// +// NRMeVipView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SnapKit + +class NRMeVipView: UIView { + + private lazy var contentView: UIView = { + let view = UIView() + view.backgroundColor = .F_2_EFEE + view.layer.cornerRadius = 10 + view.layer.masksToBounds = true + return view + }() + + private lazy var titleLabel: NRLabel = { + let label = NRLabel() + label.font = .font(ofSize: 12, weight: .semibold) + label.textColors = [UIColor.F_3912_F.cgColor, UIColor.FF_4_A_4_A.cgColor, UIColor.FA_9_B_1_F.cgColor] + label.textStartPoint = .init(x: 0, y: 0.5) + label.textEndPoint = .init(x: 1, y: 0.5) + label.setContentHuggingPriority(.required, for: .horizontal) + label.setContentCompressionResistancePriority(.required, for: .horizontal) + return label + }() + + private lazy var iconImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "time_icon_01")) + imageView.setContentHuggingPriority(.required, for: .horizontal) + imageView.setContentCompressionResistancePriority(.required, for: .horizontal) + return imageView + }() + + private lazy var timeLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + titleLabel.text = "Weekly VIP".localized + timeLabel.text = "Expiration Time:2023-11-23" + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRMeVipView { + + private func nr_setupUI() { + addSubview(contentView) + contentView.addSubview(titleLabel) + contentView.addSubview(iconImageView) + contentView.addSubview(timeLabel) + + contentView.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.height.equalTo(20) + make.centerX.equalToSuperview() + make.right.lessThanOrEqualToSuperview() + } + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(8) + } + + iconImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(titleLabel.snp.right).offset(8) + } + + timeLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(iconImageView.snp.right).offset(4) + make.right.equalToSuperview().offset(-8) + } + } + +} diff --git a/ReaderHive/Class/Me/VC/NRFeedbackViewController.swift b/ReaderHive/Class/Me/VC/NRFeedbackViewController.swift new file mode 100644 index 0000000..e73c2d3 --- /dev/null +++ b/ReaderHive/Class/Me/VC/NRFeedbackViewController.swift @@ -0,0 +1,30 @@ +// +// NRFeedbackViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit + +class NRFeedbackViewController: NRAppWebViewController { + + override func viewDidLoad() { + self.webUrl = kNRFeedBackHomeWebUrl + super.viewDidLoad() + + + } + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/ReaderHive/Class/Me/VC/NRMeViewController.swift b/ReaderHive/Class/Me/VC/NRMeViewController.swift index 87c6b90..68a498d 100644 --- a/ReaderHive/Class/Me/VC/NRMeViewController.swift +++ b/ReaderHive/Class/Me/VC/NRMeViewController.swift @@ -14,7 +14,8 @@ class NRMeViewController: NRViewController { let arr = [ NRMeItem(type: .history, icon: UIImage(named: "history_icon_01"), title: "History".localized), // NRMeItem(type: .language, icon: UIImage(named: "language_icon_01"), title: "Language".localized), - NRMeItem(type: .about, icon: UIImage(named: "about_icon_01"), title: "About".localized) + NRMeItem(type: .about, icon: UIImage(named: "about_icon_01"), title: "About".localized), + NRMeItem(type: .feedback, icon: UIImage(named: "feedback_icon_01"), title: "Feedback".localized) ] return arr }() @@ -101,6 +102,10 @@ extension NRMeViewController: UITableViewDelegate, UITableViewDataSource { let vc = NRLanguageViewController() self.navigationController?.pushViewController(vc, animated: true) + case .feedback: + let vc = NRFeedbackViewController() + self.navigationController?.pushViewController(vc, animated: true) + default: break } diff --git a/ReaderHive/Class/Store/M/NRBuyRecordsModel.swift b/ReaderHive/Class/Store/M/NRBuyRecordsModel.swift new file mode 100644 index 0000000..f512f02 --- /dev/null +++ b/ReaderHive/Class/Store/M/NRBuyRecordsModel.swift @@ -0,0 +1,23 @@ +// +// NRBuyRecordsModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SmartCodable + +class NRBuyRecordsModel: NSObject, SmartCodable { + + required override init() { } + + var created_at: String? + var short_play_id: String? + var coins: Int? + var short_play_video_id: String? + var coin_type: Int? + var image_url: String? + var name: String? + var episode: String? +} diff --git a/ReaderHive/Class/Store/M/NROrderRecordsModel.swift b/ReaderHive/Class/Store/M/NROrderRecordsModel.swift new file mode 100644 index 0000000..f705452 --- /dev/null +++ b/ReaderHive/Class/Store/M/NROrderRecordsModel.swift @@ -0,0 +1,18 @@ +// +// NROrderRecordsModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/11. +// + +import UIKit +import SmartCodable + +class NROrderRecordsModel: NSObject, SmartCodable { + required override init() { } + + var type: String? + var value: String? + var created_at: String? + +} diff --git a/ReaderHive/Class/Store/M/NRPayDateModel.swift b/ReaderHive/Class/Store/M/NRPayDateModel.swift new file mode 100644 index 0000000..825fade --- /dev/null +++ b/ReaderHive/Class/Store/M/NRPayDateModel.swift @@ -0,0 +1,156 @@ +// +// NRPayDateModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SmartCodable +import StoreKit + +class NRPayDateModel: NSObject, SmartCodable { + + enum SortName: String, SmartCaseDefaultable { + case coin = "list_coins" + case vip = "list_sub_vip" + } + + required override init() { } + + var list_coins: [NRPayItem]? + var list_sub_vip: [NRPayItem]? + var list_sub_coins: [NRPayItem]? + var sort: [SortName]? + + ///0: 老版支付 1:新版支付 + var pay_mode: Int? + ///0: 普通金币 1:金币包模式 + var show_type: Int? + +} + +class NRPayItem: NSObject, SmartCodable { + + enum VipTypeKey: String, SmartCaseDefaultable { + case week = "week" + case month = "month" + case quarter = "quarter" + case year = "year" + } + + enum SizeType: String, SmartCaseDefaultable { + case big = "big" + case small = "small" + } + + required override init() { } + + var id: String? + var status: String? + var price: String? + var origin_price: String? + var backhaul_price: String? + var coins: Int? + var send_coins: Int? + var buy_type: NRStoreAPI.BuyType? + + var vip_type: String? + var vip_type_key: VipTypeKey? + + var sort: String? + var nr_description: String? + var brief: String? + var title: String? + + var send_coin_ttl: Int? + + var size: SizeType? + + + + var ios_template_id: String? + ///角标 + var corner_marker: String? + ///平台 + var platform: String? + ///货币符号 + var currency: String? + + var ext_info: NRPayExtInfo? + + ///0 无优惠 1 首次购买优惠 2 二次购买优惠 + var discount_type: Int? + + @IgnoredKey + var product: SKProduct? + + ///首冲优惠数据 + var introductionaryOffer: SKProductDiscount? { + return product?.introductoryPrice + } + + ///促销优惠数据 + var promotionalOffers: [SKProductDiscount]? { + return product?.discounts + } + + + static func mappingForKey() -> [SmartKeyTransformer]? { + return [ + CodingKeys.nr_description <--- ["description"] + ] + } + + + + func getTimeString() -> String? { + switch self.vip_type_key { + case .week: + return "week".localized + + case .month: + return "month".localized + + case .quarter: + return "quarter".localized + + case .year: + return "year".localized + + default: + return nil + } + } + + func getVipTitle() -> String? { + switch self.vip_type_key { + case .week: + return "Weekly VIP".localized + + case .month: + return "Monthly VIP".localized + + case .quarter: + return "Quarterly VIP".localized + + case .year: + return "Yearly VIP".localized + + default: + return nil + } + } + +} + +class NRPayExtInfo: NSObject, SmartCodable { + + required override init() { } + + var receive_coins_rate: String? + var max_total_coins: Int? + var extra_day_coins: Int? + var sub_coins_txt_list: [String]? + +} diff --git a/ReaderHive/Class/Store/M/NRRewardCoinsModel.swift b/ReaderHive/Class/Store/M/NRRewardCoinsModel.swift new file mode 100644 index 0000000..706532f --- /dev/null +++ b/ReaderHive/Class/Store/M/NRRewardCoinsModel.swift @@ -0,0 +1,23 @@ +// +// NRRewardCoinsModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/11. +// + +import UIKit +import SmartCodable + +class NRRewardCoinsModel: NSObject, SmartCodable { + + required override init() { } + + var id: String? + var left_coins: String? + var expired_time: TimeInterval? + var is_effective: Int? + var created_at: String? + var type: String? + var coins: Int? + var diff_datetime: String? +} diff --git a/ReaderHive/Class/Store/V/NRConsumptionRecordsCell.swift b/ReaderHive/Class/Store/V/NRConsumptionRecordsCell.swift new file mode 100644 index 0000000..6821c22 --- /dev/null +++ b/ReaderHive/Class/Store/V/NRConsumptionRecordsCell.swift @@ -0,0 +1,41 @@ +// +// NRConsumptionRecordsCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit + +class NRConsumptionRecordsCell: NRTableViewCell { + + + var model: NRBuyRecordsModel? { + didSet { + titleLabel.text = "Purchase Single Episode".localized + subtitleLabel.text = "Ch.##".localizedReplace(text: "\(model?.episode ?? "")") + " " + "\(model?.name ?? "")" + timeLabel.text = model?.created_at + coinsLabel.text = "-\(model?.coins ?? 0)" + "Coins".localized + } + } + + @IBOutlet weak var titleLabel: UILabel! + + @IBOutlet weak var subtitleLabel: UILabel! + + @IBOutlet weak var timeLabel: UILabel! + + @IBOutlet weak var coinsLabel: UILabel! + + 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 + } + +} diff --git a/ReaderHive/Class/Store/V/NRConsumptionRecordsCell.xib b/ReaderHive/Class/Store/V/NRConsumptionRecordsCell.xib new file mode 100644 index 0000000..9decfbb --- /dev/null +++ b/ReaderHive/Class/Store/V/NRConsumptionRecordsCell.xib @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReaderHive/Class/Store/V/NROrderRecordsCell.swift b/ReaderHive/Class/Store/V/NROrderRecordsCell.swift new file mode 100644 index 0000000..4081963 --- /dev/null +++ b/ReaderHive/Class/Store/V/NROrderRecordsCell.swift @@ -0,0 +1,68 @@ +// +// NROrderRecordsCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/11. +// + +import UIKit +import SnapKit + +class NROrderRecordsCell: NRTableViewCell { + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + return label + }() + + lazy var timeLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = ._999999 + return label + }() + + lazy var countLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .F_9710_D + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + nr_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NROrderRecordsCell { + + private func nr_setupUI() { + contentView.addSubview(titleLabel) + contentView.addSubview(timeLabel) + contentView.addSubview(countLabel) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(28) + make.top.equalToSuperview().offset(16) + } + + timeLabel.snp.makeConstraints { make in + make.left.equalTo(titleLabel) + make.bottom.equalToSuperview().offset(-16) + } + + countLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-28) + } + } + +} diff --git a/ReaderHive/Class/Store/V/NRRewardCoinsCell.swift b/ReaderHive/Class/Store/V/NRRewardCoinsCell.swift new file mode 100644 index 0000000..ed319bb --- /dev/null +++ b/ReaderHive/Class/Store/V/NRRewardCoinsCell.swift @@ -0,0 +1,119 @@ +// +// NRRewardCoinsCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/11. +// + +import UIKit +import SnapKit + +class NRRewardCoinsCell: NRTableViewCell { + + var model: NRRewardCoinsModel? { + didSet { + + timeLabel.text = model?.created_at + nameLabel.text = model?.type + countLabel.text = "+\(model?.coins ?? 0)" + remainingLabel.text = model?.left_coins + + if model?.is_effective == 1 { + expiresLabel.text = "Expires in ## days".localizedReplace(text: model?.diff_datetime ?? "") + } else { + expiresLabel.text = "Expired".localized + } + } + } + + + private lazy var timeLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = ._999999 + return label + }() + + private lazy var nameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .medium) + label.textColor = .black + return label + }() + + private lazy var countLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .F_9710_D + return label + }() + + private lazy var remainingLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = ._999999 + return label + }() + + private lazy var expiresIconImageView = UIImageView(image: UIImage(named: "time_icon_02")) + + private lazy var expiresLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .F_9710_D + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRRewardCoinsCell { + + private func nr_setupUI() { + contentView.addSubview(timeLabel) + contentView.addSubview(nameLabel) + contentView.addSubview(countLabel) + contentView.addSubview(remainingLabel) + contentView.addSubview(expiresIconImageView) + contentView.addSubview(expiresLabel) + + timeLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(28) + make.top.equalToSuperview().offset(16) + } + + nameLabel.snp.makeConstraints { make in + make.left.equalTo(timeLabel) + make.centerY.equalToSuperview() + } + + expiresIconImageView.snp.makeConstraints { make in + make.left.equalTo(timeLabel) + make.bottom.equalToSuperview().offset(-16) + } + + expiresLabel.snp.makeConstraints { make in + make.centerY.equalTo(expiresIconImageView) + make.left.equalTo(expiresIconImageView.snp.right).offset(4) + } + + countLabel.snp.makeConstraints { make in + make.centerY.equalTo(nameLabel) + make.right.equalToSuperview().offset(-28) + } + + remainingLabel.snp.makeConstraints { make in + make.centerY.equalTo(expiresIconImageView) + make.right.equalTo(countLabel) + } + } + +} diff --git a/ReaderHive/Class/Store/V/NRStoreCoinsBigCell.swift b/ReaderHive/Class/Store/V/NRStoreCoinsBigCell.swift new file mode 100644 index 0000000..02ec095 --- /dev/null +++ b/ReaderHive/Class/Store/V/NRStoreCoinsBigCell.swift @@ -0,0 +1,12 @@ +// +// NRStoreCoinsBigCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit + +class NRStoreCoinsBigCell: NRStoreCoinsCell { + +} diff --git a/ReaderHive/Class/Store/V/NRStoreCoinsCell.swift b/ReaderHive/Class/Store/V/NRStoreCoinsCell.swift new file mode 100644 index 0000000..95f4231 --- /dev/null +++ b/ReaderHive/Class/Store/V/NRStoreCoinsCell.swift @@ -0,0 +1,19 @@ +// +// NRStoreCoinsCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit + +class NRStoreCoinsCell: UICollectionViewCell { + + var model: NRPayItem? { + didSet { + + } + } + + var nr_isSelected: Bool = false +} diff --git a/ReaderHive/Class/Store/V/NRStoreCoinsPackCell.swift b/ReaderHive/Class/Store/V/NRStoreCoinsPackCell.swift new file mode 100644 index 0000000..e24a0c3 --- /dev/null +++ b/ReaderHive/Class/Store/V/NRStoreCoinsPackCell.swift @@ -0,0 +1,12 @@ +// +// NRStoreCoinsPackCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit + +class NRStoreCoinsPackCell: NRStoreCoinsCell { + +} diff --git a/ReaderHive/Class/Store/V/NRStoreCoinsSmallCell.swift b/ReaderHive/Class/Store/V/NRStoreCoinsSmallCell.swift new file mode 100644 index 0000000..db8c071 --- /dev/null +++ b/ReaderHive/Class/Store/V/NRStoreCoinsSmallCell.swift @@ -0,0 +1,12 @@ +// +// NRStoreCoinsSmallCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit + +class NRStoreCoinsSmallCell: NRStoreCoinsCell { + +} diff --git a/ReaderHive/Class/Store/V/NRStoreCoinsView.swift b/ReaderHive/Class/Store/V/NRStoreCoinsView.swift new file mode 100644 index 0000000..c9b95dd --- /dev/null +++ b/ReaderHive/Class/Store/V/NRStoreCoinsView.swift @@ -0,0 +1,253 @@ +// +// NRStoreCoinsView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SnapKit + +class NRStoreCoinsView: UIView { + + var shortPlayId: String? + var videoId: String? + + var buyFinishHandle: (() -> Void)? + + var isShowTitle = false { + didSet { + updateLayout() + } + } + + private lazy var dataArr: [[NRPayItem]] = [] + + private var selectedIndexPath: IndexPath? + + private lazy var collectionViewLayout: UICollectionViewCompositionalLayout = { + let config = UICollectionViewCompositionalLayoutConfiguration() + config.interSectionSpacing = 10 + + let layout = UICollectionViewCompositionalLayout { [weak self] section, _ in + guard let self = self else { return nil} + guard let model = dataArr[section].first else { return nil } + + if model.buy_type == .subCoins { + return self.coinsBigLayoutSection() + } else if model.size == .big { + return self.bigLayoutSection() + } else { + return self.smallLayoutSection() + } + } + layout.configuration = config + + return layout + }() + + private lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.clipsToBounds = false + collectionView.isScrollEnabled = false + collectionView.register(NRStoreCoinsBigCell.self, forCellWithReuseIdentifier: "NRStoreCoinsBigCell") + collectionView.register(NRStoreCoinsSmallCell.self, forCellWithReuseIdentifier: "NRStoreCoinsSmallCell") + collectionView.register(NRStoreCoinsPackCell.self, forCellWithReuseIdentifier: "NRStoreCoinsPackCell") + collectionView.addObserver(self, forKeyPath: "contentSize", context: nil) + return collectionView + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .semibold) + label.textColor = .black + label.text = "Coins Purchase".localized + return label + }() + + deinit { + collectionView.removeObserver(self, forKeyPath: "contentSize") + } + + override init(frame: CGRect) { + super.init(frame: frame) + nr_setupUI() + updateLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "contentSize" { + updateLayout() + } + } + + func setDataArr(_ arr: [NRPayItem]) { + self.dataArr.removeAll() + var bigArr: [NRPayItem] = [] + var smallArr: [NRPayItem] = [] + var coinPackArr: [NRPayItem] = [] + + arr.forEach { + if $0.buy_type == .subCoins { + coinPackArr.append($0) + } else if $0.size == .big { + bigArr.append($0) + } else { + smallArr.append($0) + } + } + + if bigArr.count > 0 { + self.dataArr.append(bigArr) + } + if coinPackArr.count > 0 { + self.dataArr.append(coinPackArr) + } + if smallArr.count > 0 { + self.dataArr.append(smallArr) + } + self.collectionView.reloadData() + } + + private func updateLayout() { + titleLabel.isHidden = !self.isShowTitle + + let height = self.collectionView.contentSize.height + 1 + + if self.isShowTitle { + self.collectionView.snp.remakeConstraints { make in + make.top.equalTo(titleLabel.snp.bottom).offset(12) + make.left.right.bottom.equalToSuperview() + make.height.equalTo(height) + } + } else { + self.collectionView.snp.remakeConstraints { make in + make.top.equalToSuperview().offset(8) + make.left.right.bottom.equalToSuperview() + make.height.equalTo(height) + } + } + } + +} + +extension NRStoreCoinsView { + + private func nr_setupUI() { + addSubview(titleLabel) + addSubview(collectionView) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview() + } + + + } + +} + +extension NRStoreCoinsView { + + private func bigLayoutSection() -> NSCollectionLayoutSection { + let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1))) + + let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(100)), subitems: [item]) + group.interItemSpacing = .fixed(1) + + let layoutSection = NSCollectionLayoutSection(group: group) + layoutSection.interGroupSpacing = 10 + layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16) + return layoutSection + } + + private func smallLayoutSection() -> NSCollectionLayoutSection { + let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1 / 3), heightDimension: .fractionalHeight(1))) + + let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(121)), subitems: [item]) + group.interItemSpacing = .fixed(8) + + let layoutSection = NSCollectionLayoutSection(group: group) + layoutSection.interGroupSpacing = 10 + layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16) + return layoutSection + } + + private func coinsBigLayoutSection() -> NSCollectionLayoutSection { + let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))) + + let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(84)), subitems: [item]) + + + let layoutSection = NSCollectionLayoutSection(group: group) + layoutSection.interGroupSpacing = 10 + layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16) + return layoutSection + } +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRStoreCoinsView: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let model = dataArr[indexPath.section][indexPath.row] + + var identifier = "NRStoreCoinsBigCell" + if model.buy_type == .subCoins { + identifier = "NRStoreCoinsPackCell" + } else if model.size == .small { + identifier = "NRStoreCoinsSmallCell" + } + + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! NRStoreCoinsCell + cell.model = model + cell.nr_isSelected = selectedIndexPath == indexPath + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataArr[section].count + } + + func numberOfSections(in collectionView: UICollectionView) -> Int { + return dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let model = dataArr[indexPath.section][indexPath.row] + self.selectedIndexPath = indexPath + collectionView.reloadData() + + if model.buy_type == .subCoins { +// let view = FACoinPackConfirmView() +// view.shortPlayId = self.shortPlayId +// view.videoId = self.videoId +// view.model = model +// view.buyFinishHandle = { [weak self] in +// guard let self = self else { return } +// NRLoginManager.manager.updateUserInfo() +// self.buyFinishHandle?() +// } +// view.present(in: nil) + } else { + NRIapManager.manager.start(model: model, shortPlayId: self.shortPlayId, videoId: self.videoId) { [weak self] finish in + guard let self = self else { return } + if finish { + Task { + await NRLoginManager.manager.updateUserInfo() + } + self.buyFinishHandle?() + } + } + } + + + } + +} diff --git a/ReaderHive/Class/Store/V/NRStoreVipCell.swift b/ReaderHive/Class/Store/V/NRStoreVipCell.swift new file mode 100644 index 0000000..64bdf1a --- /dev/null +++ b/ReaderHive/Class/Store/V/NRStoreVipCell.swift @@ -0,0 +1,16 @@ +// +// NRStoreVipCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit + +class NRStoreVipCell: UICollectionViewCell { + var model: NRPayItem? { + didSet { + + } + } +} diff --git a/ReaderHive/Class/Store/V/NRStoreVipView.swift b/ReaderHive/Class/Store/V/NRStoreVipView.swift new file mode 100644 index 0000000..e2a1c48 --- /dev/null +++ b/ReaderHive/Class/Store/V/NRStoreVipView.swift @@ -0,0 +1,166 @@ +// +// NRStoreVipView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SnapKit + +class NRStoreVipView: UIView { + + var dataArr: [NRPayItem] = [] { + didSet { + collectionView.reloadData() + } + } + + var shortPlayId: String? + var videoId: String? + + var buyFinishHandle: (() -> Void)? + + var isShowTitle = false { + didSet { + updateLayout() + } + } + + private lazy var collectionViewLayout: UICollectionViewCompositionalLayout = { + + let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))) + + let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(110)), subitems: [item]) + + let layoutSection = NSCollectionLayoutSection(group: group) + layoutSection.interGroupSpacing = 14 + layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16) + + + let config = UICollectionViewCompositionalLayoutConfiguration() + + let layout = UICollectionViewCompositionalLayout(section: layoutSection) + layout.configuration = config + + return layout + }() + + private lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.isScrollEnabled = false + collectionView.addObserver(self, forKeyPath: "contentSize", context: nil) + collectionView.register(NRStoreVipCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .semibold) + label.textColor = .black + label.text = "VIP Membership".localized + return label + }() + + private lazy var subtitleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black.withAlphaComponent(0.25) + label.text = "Auto renew,cancel anytime".localized + return label + }() + + deinit { + collectionView.removeObserver(self, forKeyPath: "contentSize") + } + + override init(frame: CGRect) { + super.init(frame: frame) + nr_setupUI() + updateLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "contentSize" { + self.updateLayout() + } + } + + private func updateLayout() { + titleLabel.isHidden = !self.isShowTitle + subtitleLabel.isHidden = !self.isShowTitle + + let height = self.collectionView.contentSize.height + 1 + + if self.isShowTitle { + self.collectionView.snp.remakeConstraints { make in + make.top.equalTo(subtitleLabel.snp.bottom).offset(12) + make.left.right.bottom.equalToSuperview() + make.height.equalTo(height) + } + } else { + self.collectionView.snp.remakeConstraints { make in + make.top.equalToSuperview().offset(8) + make.left.right.bottom.equalToSuperview() + make.height.equalTo(height) + } + } + } +} + +extension NRStoreVipView { + + private func nr_setupUI() { + addSubview(titleLabel) + addSubview(subtitleLabel) + addSubview(collectionView) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview() + } + + subtitleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalTo(titleLabel.snp.bottom).offset(5) + } + + } + +} + + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRStoreVipView: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRStoreVipCell + cell.model = self.dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let model = self.dataArr[indexPath.row] + + NRIapManager.manager.start(model: model, shortPlayId: self.shortPlayId, videoId: self.videoId) { [weak self] finish in + guard let self = self else { return } + if finish { + Task { + await NRLoginManager.manager.updateUserInfo() + } + self.buyFinishHandle?() + } + } + } + +} diff --git a/ReaderHive/Class/Store/V/NRWalletCell.swift b/ReaderHive/Class/Store/V/NRWalletCell.swift new file mode 100644 index 0000000..852e1fe --- /dev/null +++ b/ReaderHive/Class/Store/V/NRWalletCell.swift @@ -0,0 +1,49 @@ +// +// NRWalletCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SnapKit + +class NRWalletCell: NRTableViewCell { + + var item: NRMeItem? { + didSet { + titleLabel.text = item?.title + } + } + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + nr_indicatorImageView.image = UIImage(named: "arrow_right_icon_06") + + contentView.addSubview(titleLabel) + contentView.addSubview(nr_indicatorImageView) + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(16) + } + + nr_indicatorImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-16) + } + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/ReaderHive/Class/Store/V/NRWalletHeaderView.swift b/ReaderHive/Class/Store/V/NRWalletHeaderView.swift new file mode 100644 index 0000000..ac85a66 --- /dev/null +++ b/ReaderHive/Class/Store/V/NRWalletHeaderView.swift @@ -0,0 +1,186 @@ +// +// NRWalletHeaderView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SnapKit +import YYCategories + +class NRWalletHeaderView: UITableViewHeaderFooterView { + + var userInfo: NRUserInfo? { + didSet { + coinsView.count = userInfo?.coin_left_total + bonusCoinsView.count = userInfo?.send_coin_left_total + } + } + + private lazy var bgView: UIView = { + let view = UIView() + view.backgroundColor = .F_2_EFEE + view.layer.cornerRadius = 12 + view.layer.masksToBounds = true + return view + }() + + private lazy var storeButton: UIButton = { + let button = NRGradientButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + let vc = NRStoreViewController() + self.viewController?.navigationController?.pushViewController(vc, animated: true) + })) + button.colors = [UIColor.F_3912_F.cgColor, UIColor.FF_4_A_4_A.cgColor, UIColor.FA_9_B_1_F.cgColor] + button.startPoint = .init(x: 0, y: 0.5) + button.endPoint = .init(x: 1, y: 0.5) + button.layer.cornerRadius = 24 + button.layer.masksToBounds = true + button.setTitle("Store".localized, for: .normal) + button.setTitleColor(.white, for: .normal) + button.titleLabel?.font = .font(ofSize: 14, weight: .medium) + return button + }() + + private lazy var coinsView: CoinsView = { + let view = CoinsView() + view.title = "Coins".localized + return view + }() + + private lazy var bonusCoinsView: CoinsView = { + let view = CoinsView() + view.title = "Bonus".localized + return view + }() + + override init(reuseIdentifier: String?) { + super.init(reuseIdentifier: reuseIdentifier) + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRWalletHeaderView { + + private func nr_setupUI() { + contentView.addSubview(bgView) + bgView.addSubview(storeButton) + bgView.addSubview(coinsView) + bgView.addSubview(bonusCoinsView) + + bgView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.right.equalToSuperview().offset(-16) + make.top.equalToSuperview().offset(24) + make.bottom.equalToSuperview().offset(-12) + make.height.equalTo(144) + } + + storeButton.snp.makeConstraints { make in + make.left.equalToSuperview().offset(12) + make.right.equalToSuperview().offset(-12) + make.bottom.equalToSuperview().offset(-16) + make.height.equalTo(48) + } + + coinsView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(12) + make.top.equalToSuperview().offset(24) + } + + bonusCoinsView.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-12) + make.top.equalTo(coinsView) + make.width.equalTo(coinsView) + make.left.equalTo(coinsView.snp.right).offset(0) + } + } + +} + + +extension NRWalletHeaderView { + + class CoinsView: UIView { + + var title: String? { + didSet { + titleLabel.text = title + } + } + + var count: Int? { + didSet { + coinsLabel.text = "\(count ?? 0)" + } + } + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .semibold) + label.textColor = .black.withAlphaComponent(0.5) + return label + }() + + lazy var coinsBgView: UIView = { + let view = UIView() + return view + }() + + lazy var coinsImageView = UIImageView(image: UIImage(named: "coins_icon_01")) + + lazy var coinsLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 18, weight: .regular) + label.textColor = .F_9710_D + label.text = "0" + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + addSubview(titleLabel) + addSubview(coinsBgView) + coinsBgView.addSubview(coinsImageView) + coinsBgView.addSubview(coinsLabel) + + titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview() + make.centerX.equalToSuperview() + make.right.lessThanOrEqualToSuperview() + } + + coinsBgView.snp.makeConstraints { make in + make.bottom.equalToSuperview() + make.centerX.equalToSuperview() + make.right.lessThanOrEqualToSuperview() + make.top.equalTo(titleLabel.snp.bottom).offset(10) + } + + coinsImageView.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.left.equalToSuperview() + } + + coinsLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(coinsImageView.snp.right).offset(4) + make.right.equalToSuperview() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + } + +} diff --git a/ReaderHive/Class/Store/VC/NRConsumptionRecordsViewController.swift b/ReaderHive/Class/Store/VC/NRConsumptionRecordsViewController.swift new file mode 100644 index 0000000..71b6c76 --- /dev/null +++ b/ReaderHive/Class/Store/VC/NRConsumptionRecordsViewController.swift @@ -0,0 +1,117 @@ +// +// NRConsumptionRecordsViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SnapKit +import LYEmptyView + +class NRConsumptionRecordsViewController: NRViewController { + + private lazy var dataArr: [NRBuyRecordsModel] = [] + private lazy var page = 1 + + private lazy var tableView: NRTableView = { + let tableView = NRTableView(frame: .zero, style: .plain) + tableView.delegate = self + tableView.dataSource = self + tableView.rowHeight = 74 + tableView.separatorColor = .F_2_EFEE + tableView.separatorInset = .init(top: 0, left: 16, bottom: 0, right: 16) + tableView.contentInset = .init(top: 16, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) + tableView.register(UINib(nibName: "NRConsumptionRecordsCell", bundle: nil), forCellReuseIdentifier: "cell") + tableView.ly_emptyView = NREmpty.nr_emptyView() + tableView.nr_addRefreshHeader(insetTop: tableView.contentInset.top) { [weak self] in + self?.handleHeaderRefresh(nil) + } + tableView.nr_addRefreshFooter(insetBottom: tableView.contentInset.bottom) { [weak self] in + self?.handleFooterRefresh(nil) + } + return tableView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.title = "Consumption Records".localized + self.backgroundImageView.isHidden = true + configNavigationBack("arrow_left_icon_05") + + nr_setupUI() + + requestDataArr(page: 1, completer: nil) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + self.nr_setNavigationStyle(titleColor: UINavigationBar.titleBlackColor) + } + + override func handleHeaderRefresh(_ completer: (() -> Void)?) { + requestDataArr(page: 1) { [weak self] in + self?.tableView.nr_endHeaderRefreshing() + } + } + + override func handleFooterRefresh(_ completer: (() -> Void)?) { + requestDataArr(page: self.page + 1) { [weak self] in + self?.tableView.nr_endFooterRefreshing() + } + } + +} + +extension NRConsumptionRecordsViewController { + + private func nr_setupUI() { + view.addSubview(tableView) + + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + } + } + +} + + +//MARK: UITableViewDelegate UITableViewDataSource +extension NRConsumptionRecordsViewController: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! NRConsumptionRecordsCell + cell.model = dataArr[indexPath.row] + return cell + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return dataArr.count + } +} + +extension NRConsumptionRecordsViewController { + + private func requestDataArr(page: Int, completer: (() -> Void)?) { + NRStoreAPI.requestBuyRecords(page: page) { [weak self] listModel in + guard let self = self else { return } + guard let listModel = listModel, let list = listModel.list else { + completer?() + return + } + if page == 1 { + self.dataArr.removeAll() + } + self.dataArr += list + + self.page = page + self.tableView.reloadData() + + completer?() + } + + } + +} diff --git a/ReaderHive/Class/Store/VC/NROrderRecordsPageViewController.swift b/ReaderHive/Class/Store/VC/NROrderRecordsPageViewController.swift new file mode 100644 index 0000000..7a0ff60 --- /dev/null +++ b/ReaderHive/Class/Store/VC/NROrderRecordsPageViewController.swift @@ -0,0 +1,118 @@ +// +// NROrderRecordsViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/11. +// + +import UIKit +import JXSegmentedView +import SnapKit + +class NROrderRecordsPageViewController: NRViewController { + + private lazy var titles = ["Coin Record".localized, "VIP Record".localized] + private lazy var viewControllers: [NROrderRecordsViewController] = { + let vc1 = NROrderRecordsViewController() + vc1.type = .coins + let vc2 = NROrderRecordsViewController() + vc2.type = .subVip + return [vc1, vc2] + }() + + private lazy var segmentedDataSource: JXSegmentedTitleDataSource = { + let dataSource = JXSegmentedTitleDataSource() + dataSource.itemWidth = (UIScreen.width - 32 - 18) / 2 + dataSource.titles = titles + dataSource.isTitleMaskEnabled = true + dataSource.titleNormalColor = .black.withAlphaComponent(0.5) + dataSource.titleSelectedColor = .white + dataSource.titleNormalFont = .font(ofSize: 14, weight: .medium) + dataSource.titleSelectedFont = .font(ofSize: 14, weight: .medium) + dataSource.itemSpacing = 0 + return dataSource + }() + + private lazy var segmentedIndicator: JXSegmentedIndicatorBackgroundView = { + let indicator = JXSegmentedIndicatorBackgroundView() + indicator.indicatorHeight = 36 + indicator.indicatorWidthIncrement = 0 + indicator.indicatorColor = .F_9710_D + return indicator + }() + + private lazy var segmentedView: JXSegmentedView = { + let view = JXSegmentedView() + view.backgroundColor = .black.withAlphaComponent(0.05) + view.layer.cornerRadius = 24 + view.layer.masksToBounds = true + view.dataSource = segmentedDataSource + view.delegate = self + view.indicators = [segmentedIndicator] + view.listContainer = listContainerView + return view + }() + + private lazy var listContainerView: JXSegmentedListContainerView = { + let view = JXSegmentedListContainerView(dataSource: self) + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.title = "Order Records".localized + self.backgroundImageView.isHidden = true + configNavigationBack("arrow_left_icon_05") + + nr_setupUI() + } + + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + self.nr_setNavigationStyle(titleColor: UINavigationBar.titleBlackColor) + } + +} + +extension NROrderRecordsPageViewController { + + private func nr_setupUI() { + view.addSubview(segmentedView) + view.addSubview(listContainerView) + + segmentedView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(16 + UIScreen.navBarHeight) + make.height.equalTo(48) + } + + listContainerView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(segmentedView.snp.bottom) + } + } + + +} + +//MARK: JXSegmentedViewDelegate +extension NROrderRecordsPageViewController: JXSegmentedViewDelegate { + func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int) { + + } +} + +//MARK: JXSegmentedViewDelegate +extension NROrderRecordsPageViewController: JXSegmentedListContainerViewDataSource { + func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> any JXSegmentedListContainerViewListDelegate { + return viewControllers[index] + } + + func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int { + return self.titles.count + } + +} diff --git a/ReaderHive/Class/Store/VC/NROrderRecordsViewController.swift b/ReaderHive/Class/Store/VC/NROrderRecordsViewController.swift new file mode 100644 index 0000000..6d7bdd8 --- /dev/null +++ b/ReaderHive/Class/Store/VC/NROrderRecordsViewController.swift @@ -0,0 +1,108 @@ +// +// NROrderRecordsViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/11. +// + +import UIKit +import LYEmptyView +import SnapKit + +class NROrderRecordsViewController: NRViewController { + + var type: NRStoreAPI.BuyType = .coins + + private lazy var dataArr: [NROrderRecordsModel] = [] + private lazy var page = 1 + + private lazy var tableView: NRTableView = { + let tableView = NRTableView(frame: .zero, style: .plain) + tableView.delegate = self + tableView.dataSource = self + tableView.rowHeight = 74 + tableView.separatorColor = .F_2_EFEE + tableView.separatorInset = .init(top: 0, left: 16, bottom: 0, right: 16) + tableView.contentInset = .init(top: 6, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) + tableView.register(NROrderRecordsCell.self, forCellReuseIdentifier: "cell") + tableView.ly_emptyView = NREmpty.nr_emptyView() + tableView.nr_addRefreshHeader(insetTop: tableView.contentInset.top) { [weak self] in + self?.handleHeaderRefresh(nil) + } + tableView.nr_addRefreshFooter(insetBottom: tableView.contentInset.bottom) { [weak self] in + self?.handleFooterRefresh(nil) + } + return tableView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.backgroundImageView.isHidden = true + self.view.backgroundColor = .clear + + view.addSubview(tableView) + + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(10) + } + + requestDataArr(page: 1, completer: nil) + } + + override func handleHeaderRefresh(_ completer: (() -> Void)?) { + requestDataArr(page: 1) { [weak self] in + self?.tableView.nr_endHeaderRefreshing() + } + } + + override func handleFooterRefresh(_ completer: (() -> Void)?) { + requestDataArr(page: self.page + 1) { [weak self] in + self?.tableView.nr_endFooterRefreshing() + } + } +} + + +//MARK: UITableViewDelegate UITableViewDataSource +extension NROrderRecordsViewController: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let model = self.dataArr[indexPath.row] + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! NROrderRecordsCell + cell.titleLabel.text = model.type + cell.timeLabel.text = model.created_at + cell.countLabel.text = "+\(model.value ?? "0")" + return cell + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.dataArr.count + } + +} + +extension NROrderRecordsViewController { + + private func requestDataArr(page: Int, completer: (() -> Void)?) { + NRStoreAPI.requestRechargeRecord(page: page, buyType: self.type) { [weak self] listModel in + guard let self = self else { return } + guard let listModel = listModel, let list = listModel.list else { + completer?() + return + } + if page == 1 { + self.dataArr.removeAll() + } + self.dataArr += list + + self.page = page + self.tableView.reloadData() + + completer?() + } + + } + +} + diff --git a/ReaderHive/Class/Store/VC/NRRewardCoinsViewController.swift b/ReaderHive/Class/Store/VC/NRRewardCoinsViewController.swift new file mode 100644 index 0000000..287762d --- /dev/null +++ b/ReaderHive/Class/Store/VC/NRRewardCoinsViewController.swift @@ -0,0 +1,107 @@ +// +// NRRewardCoinsViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import LYEmptyView +import SnapKit + +class NRRewardCoinsViewController: NRViewController { + + private lazy var dataArr: [NRRewardCoinsModel] = [] + private lazy var page = 1 + + private lazy var tableView: NRTableView = { + let tableView = NRTableView(frame: .zero, style: .plain) + tableView.delegate = self + tableView.dataSource = self + tableView.rowHeight = 98 + tableView.separatorColor = .F_2_EFEE + tableView.separatorInset = .init(top: 0, left: 16, bottom: 0, right: 16) + tableView.contentInset = .init(top: 16, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) + tableView.register(NRRewardCoinsCell.self, forCellReuseIdentifier: "cell") + tableView.ly_emptyView = NREmpty.nr_emptyView() + tableView.nr_addRefreshHeader(insetTop: tableView.contentInset.top) { [weak self] in + self?.handleHeaderRefresh(nil) + } + tableView.nr_addRefreshFooter(insetBottom: tableView.contentInset.bottom) { [weak self] in + self?.handleFooterRefresh(nil) + } + return tableView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.title = "Reward Coins".localized + self.backgroundImageView.isHidden = true + configNavigationBack("arrow_left_icon_05") + + view.addSubview(tableView) + + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + } + + requestDataArr(page: 1, completer: nil) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + self.nr_setNavigationStyle(titleColor: UINavigationBar.titleBlackColor) + } + + override func handleHeaderRefresh(_ completer: (() -> Void)?) { + requestDataArr(page: 1) { [weak self] in + self?.tableView.nr_endHeaderRefreshing() + } + } + + override func handleFooterRefresh(_ completer: (() -> Void)?) { + requestDataArr(page: self.page + 1) { [weak self] in + self?.tableView.nr_endFooterRefreshing() + } + } +} + +//MARK: UITableViewDelegate UITableViewDataSource +extension NRRewardCoinsViewController: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! NRRewardCoinsCell + cell.model = self.dataArr[indexPath.row] + return cell + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.dataArr.count + } +} + +extension NRRewardCoinsViewController { + + private func requestDataArr(page: Int, completer: (() -> Void)?) { + NRStoreAPI.reuqestSendCoinRecord(page: page) { [weak self] listModel in + guard let self = self else { return } + guard let listModel = listModel, let list = listModel.list else { + completer?() + return + } + if page == 1 { + self.dataArr.removeAll() + } + self.dataArr += list + + self.page = page + self.tableView.reloadData() + + completer?() + } + + } + +} + diff --git a/ReaderHive/Class/Store/VC/NRStoreViewController.swift b/ReaderHive/Class/Store/VC/NRStoreViewController.swift new file mode 100644 index 0000000..c010706 --- /dev/null +++ b/ReaderHive/Class/Store/VC/NRStoreViewController.swift @@ -0,0 +1,82 @@ +// +// NRStoreViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SnapKit + +class NRStoreViewController: NRViewController { + + + private lazy var scrollView: NRScrollView = { + let scrollView = NRScrollView() + return scrollView + }() + + private lazy var stackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = 12 + return stackView + }() + + private lazy var coinsView: NRStoreCoinsView = { + let view = NRStoreCoinsView() + view.isShowTitle = true + view.buyFinishHandle = { [weak self] in + + } + return view + }() + + private lazy var vipView: NRStoreVipView = { + let view = NRStoreVipView() + view.isShowTitle = true + view.buyFinishHandle = { [weak self] in + + } + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.title = "Store".localized + configNavigationBack("arrow_left_icon_05") + + stackView.addArrangedSubview(coinsView) + stackView.addArrangedSubview(vipView) + + nr_setupUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + self.nr_setNavigationStyle(titleColor: UINavigationBar.titleBlackColor) + } + +} + +extension NRStoreViewController { + + private func nr_setupUI() { + view.addSubview(scrollView) + scrollView.addSubview(stackView) + + scrollView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + } + + stackView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(12) + make.left.equalToSuperview() + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().offset(-(UIScreen.safeBottom + 10)) + } + } + +} diff --git a/ReaderHive/Class/Store/VC/NRWalletViewController.swift b/ReaderHive/Class/Store/VC/NRWalletViewController.swift new file mode 100644 index 0000000..324b499 --- /dev/null +++ b/ReaderHive/Class/Store/VC/NRWalletViewController.swift @@ -0,0 +1,114 @@ +// +// NRWalletViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SnapKit + +class NRWalletViewController: NRViewController { + + private lazy var dataArr: [NRMeItem] = { + let arr = [ + NRMeItem(type: .consumptionRecords, title: "Consumption Records".localized), + NRMeItem(type: .purchaseRecords, title: "Purchase Records".localized), + NRMeItem(type: .rewardCoins, title: "Reward Coins".localized), + ] + return arr + }() + + + private lazy var tableView: NRTableView = { + let tableView = NRTableView(frame: .zero, style: .grouped) + tableView.delegate = self + tableView.dataSource = self + tableView.rowHeight = 60 + tableView.separatorStyle = .none + tableView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) + tableView.register(NRWalletCell.self, forCellReuseIdentifier: "cell") + tableView.register(NRWalletHeaderView.self, forHeaderFooterViewReuseIdentifier: "header") + return tableView + }() + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidLoad() { + super.viewDidLoad() + self.title = "My Wallet".localized + self.backgroundImageView.isHidden = true + configNavigationBack("arrow_left_icon_05") + + NotificationCenter.default.addObserver(self, selector: #selector(userInfoUpdateNotification), name: NRLoginManager.userInfoUpdateNotification, object: nil) + + nr_setupUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + self.nr_setNavigationStyle(titleColor: UINavigationBar.titleBlackColor) + } + + @objc private func userInfoUpdateNotification() { + self.tableView.reloadData() + } +} + +extension NRWalletViewController { + + private func nr_setupUI() { + view.addSubview(tableView) + + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + } + } + +} + +//MARK: UITableViewDelegate UITableViewDataSource +extension NRWalletViewController: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! NRWalletCell + cell.item = dataArr[indexPath.row] + return cell + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return dataArr.count + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header") as! NRWalletHeaderView + view.userInfo = NRLoginManager.manager.userInfo + return view + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let item = self.dataArr[indexPath.row] + + switch item.type { + case .consumptionRecords: + let vc = NRConsumptionRecordsViewController() + self.navigationController?.pushViewController(vc, animated: true) + + case .rewardCoins: + let vc = NRRewardCoinsViewController() + self.navigationController?.pushViewController(vc, animated: true) + + case .purchaseRecords: + let vc = NROrderRecordsPageViewController() + self.navigationController?.pushViewController(vc, animated: true) + + default: + break + } + + } +} diff --git a/ReaderHive/Delegate/AppDelegate.swift b/ReaderHive/Delegate/AppDelegate.swift index 65bd1d9..08714c9 100644 --- a/ReaderHive/Delegate/AppDelegate.swift +++ b/ReaderHive/Delegate/AppDelegate.swift @@ -23,6 +23,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { Task { await NRLoginManager.manager.updateUserInfo() } + NRIapManager.manager.preloadingProducts() return true } @@ -43,9 +44,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { @objc private func networkStatusDidChangeNotification() { + guard NRNetworkReachableManager.manager.isReachable == true else { + return + } + Task { await NRLoginManager.manager.updateUserInfo() } + NRIapManager.manager.preloadingProducts() } } diff --git a/ReaderHive/Libs/IAP/NRIAPOrderModel.swift b/ReaderHive/Libs/IAP/NRIAPOrderModel.swift new file mode 100644 index 0000000..0af2aff --- /dev/null +++ b/ReaderHive/Libs/IAP/NRIAPOrderModel.swift @@ -0,0 +1,41 @@ +// +// NRIAPOrderModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SmartCodable + +class NRIAPOrderModel: NSObject, SmartCodable { + + required override init() { } + + var code: Int? + var message: String? + var money: String? + var order_code: String? + var is_backhaul: String? + var discount: NRIAPOrderDiscountModel? +} + +class NRIAPOrderDiscountModel: NSObject, SmartCodable { + + required override init() { } + + var is_discount: Bool? + var discount_code: String? + var sign_data: NRIAPOrderDiscountSign? +} + +class NRIAPOrderDiscountSign: NSObject, SmartCodable { + + required override init() { } + + var keyIdentifier: String? + var nonce: String? + var timestamp: TimeInterval? + var signature: String? + var applicationUsername: String? +} diff --git a/ReaderHive/Libs/IAP/NRIAPVerifyModel.swift b/ReaderHive/Libs/IAP/NRIAPVerifyModel.swift new file mode 100644 index 0000000..342408a --- /dev/null +++ b/ReaderHive/Libs/IAP/NRIAPVerifyModel.swift @@ -0,0 +1,19 @@ +// +// NRIAPVerifyModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import SmartCodable + +class NRIAPVerifyModel: NSObject, SmartCodable { + + required override init() { } + + var code: String? + var is_backhaul: String? + var money: String? + var status: String? +} diff --git a/ReaderHive/Libs/IAP/NRIapManager.swift b/ReaderHive/Libs/IAP/NRIapManager.swift new file mode 100644 index 0000000..b573220 --- /dev/null +++ b/ReaderHive/Libs/IAP/NRIapManager.swift @@ -0,0 +1,293 @@ +// +// NRIapManager.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import StoreKit + +class NRIapManager { + typealias CompletionHandler = ((_ finish: Bool) -> Void) + ///内购模版前缀 + static let IAPPrefix = "readerhive" + + + static let manager = NRIapManager() + + ///成功回调 + private var completionHandler: CompletionHandler? + + private var shortPlayId: String? + private var videoId: String? + + private lazy var iapManager: JXIAPManager = { + let manager = JXIAPManager() + manager.delegate = self + return manager + }() + + private var orderCode: String? + private var payId: String? + + ///预加载数据 + private var payRequest: NRPayDataRequest? + var payDateModel: NRPayDateModel? + + + ///恢复购买使用 + ///等待恢复的数据 + private var waitRestoreModel: NRWaitRestoreModel? = UserDefaults.nr_object(forKey: kNRWaitRestoreIAPDefaultsKey, as: NRWaitRestoreModel.self) + + + ///开始内购 + func start(model: NRPayItem, shortPlayId: String? = nil, videoId: String? = nil, hudShowView: UIView? = nil, handler: CompletionHandler? = nil) { + + if let _ = self.waitRestoreModel { + NRToast.show(text: "pay_error_2".localized) + handler?(false) + return + } + + guard let payId = model.id else { + handler?(false) + return + } + self.shortPlayId = shortPlayId + self.videoId = videoId + self.completionHandler = handler + self.waitRestoreModel = NRWaitRestoreModel() + self.waitRestoreModel?.buyType = model.buy_type + let productId = getProductId(templateId: model.ios_template_id) ?? "" + var isDiscount = false + var identifierDiscount: String? = nil + if model.discount_type == 1, let _ = model.introductionaryOffer { + isDiscount = true + } else if model.discount_type == 2, let discount = model.promotionalOffers?.first { + isDiscount = true + identifierDiscount = discount.identifier + } + + NRHud.show(containerView: hudShowView) + + NRStoreAPI.requestCreateOrder(payId: payId, shortPlayId: shortPlayId ?? "0", videoId: videoId ?? "0", isDiscount: isDiscount, identifierDiscount: identifierDiscount) { orderModel in + guard let orderModel = orderModel else { + NRHud.dismiss() + self.waitRestoreModel = nil + self.completionHandler?(false) + self.clean() + return + } + self.orderCode = orderModel.order_code + self.payId = payId + self.waitRestoreModel?.payId = payId + self.waitRestoreModel?.orderCode = orderModel.order_code + + var discount: SKPaymentDiscount? = nil + + if let identifierDiscount = identifierDiscount, + let signData = orderModel.discount?.sign_data, + let keyIdentifier = signData.keyIdentifier, + let nonce = UUID(uuidString: signData.nonce ?? ""), + let signature = signData.signature, + let timestamp = signData.timestamp + { + discount = SKPaymentDiscount(identifier: identifierDiscount, + keyIdentifier: keyIdentifier, + nonce: nonce, + signature: signature, + timestamp: NSNumber(value: timestamp)) + } + + self.iapManager.start(productId: productId, orderId: self.orderCode ?? "", applicationUsername: orderModel.discount?.sign_data?.applicationUsername, discount: discount) + + } + + } + + func restore(isLoding: Bool = true, shortPlayId: String? = nil, videoId: String? = nil, completer: ((_ isFinish: Bool, _ buyType: NRStoreAPI.BuyType?) -> Void)?) { + let buyType = self.waitRestoreModel?.buyType + + guard let waitRestoreModel = self.waitRestoreModel, + let orderCode = waitRestoreModel.orderCode, + let payId = waitRestoreModel.payId, + let receipt = waitRestoreModel.receipt, + let transactionId = waitRestoreModel.transactionId + else { + if isLoding { + NRToast.show(text: "pay_error_3".localized) + } + completer?(false, buyType) + return + } + + if isLoding { + NRHud.show() + } + + let verifyData = self.getVerifyOrderParameters(orderCode: orderCode, payId: payId, transactionId: transactionId, purchaseToken: receipt) + + let statParamenters: [String : Any] = [ + "type" : isLoding ? "manual" : "auto", + "pay_data" : verifyData.toJsonString() ?? "" + ] +// NRStatAPI.requestEventStat(orderCode: orderCode, shortPlayId: shortPlayId, videoId: videoId, eventKey: .payRestore, errorMsg: "restore", otherParamenters: statParamenters) + + NRStoreAPI.requestVerifyOrder(parameters: verifyData) { response in + if isLoding { + NRHud.dismiss() + } + + guard let model = response.data else { + completer?(false, buyType) + return + } + self.waitRestoreModel = nil + UserDefaults.nr_setObject(nil, forKey: kNRWaitRestoreIAPDefaultsKey) + + if model.status == "success" { + if buyType == .subVip { + NRLoginManager.manager.userInfo?.is_vip = true + } + + if isLoding { + NRToast.show(text: "Success".localized) + } + completer?(true, buyType) + if buyType == .subVip { + NotificationCenter.default.post(name: NRIapManager.buyVipFinishNotification, object: nil) + } + } else { + completer?(false, buyType) + } + } + + } + + + func getProductId(templateId: String?) -> String? { + guard let templateId = templateId else { return nil } + return NRIapManager.IAPPrefix + "." + templateId + } + + func clean() { + self.orderCode = nil + self.payId = nil + self.shortPlayId = nil + self.videoId = nil + self.completionHandler = nil + } +} + +//MARK: JXIAPManagerDelegate +extension NRIapManager: JXIAPManagerDelegate { + + func jx_iapPaySuccess(productId: String, receipt: String, transactionIdentifier: String) { + guard let orderCode = self.orderCode, let payId = self.payId else { + self.waitRestoreModel = nil + self.completionHandler?(false) + self.clean() + NRHud.dismiss() + return + } + + self.waitRestoreModel?.productId = productId + self.waitRestoreModel?.receipt = receipt + self.waitRestoreModel?.transactionId = transactionIdentifier + + UserDefaults.nr_setObject(self.waitRestoreModel, forKey: kNRWaitRestoreIAPDefaultsKey) + + #if DEBUG + let verifyData = self.getVerifyOrderParameters(orderCode: orderCode, payId: payId, transactionId: transactionIdentifier, purchaseToken: receipt) + #else + let verifyData = self.getVerifyOrderParameters(orderCode: orderCode, payId: payId, transactionId: transactionIdentifier, purchaseToken: receipt) + #endif + + NRStoreAPI.requestVerifyOrder(parameters: verifyData) { response in + NRHud.dismiss() + + guard let model = response.data else { +// FAStatAPI.requestEventStat(orderCode: self.orderCode, shortPlayId: self.shortPlayId, videoId: self.videoId, eventKey: .payCallback, errorMsg: verifyData.toJsonString()) + self.completionHandler?(false) + self.clean() + return + } + + let buyType = self.waitRestoreModel?.buyType + self.waitRestoreModel = nil + UserDefaults.nr_setObject(nil, forKey: kNRWaitRestoreIAPDefaultsKey) + + if model.status == "success" { + if buyType == .subVip { + NRLoginManager.manager.userInfo?.is_vip = true + } + + NRToast.show(text: "Success".localized) + self.completionHandler?(true) + if buyType == .subVip { + NotificationCenter.default.post(name: NRIapManager.buyVipFinishNotification, object: nil) + } + } else { + NRToast.show(text: "pay_error_4".localized) +// FAStatAPI.requestEventStat(orderCode: self.orderCode, shortPlayId: self.shortPlayId, videoId: self.videoId, eventKey: .payCallback, errorMsg: verifyData.toJsonString()) + self.completionHandler?(false) + } + self.clean() + } + + } + + func jx_iapPayFailed(productId: String, code: JXIAPManagerCode, msg: String?) { + NRHud.dismiss() + + if code == .noProduct { + NRToast.show(text: "pay_error_5".localized) + } else if code == .cancelled { + NRToast.show(text: "pay_error_6".localized) + } + +// if code == .cancelled { +// FAStatAPI.requestEventStat(orderCode: self.orderCode, shortPlayId: self.shortPlayId, videoId: self.videoId, eventKey: .payCancel, errorMsg: "user cancel") +// } else { +// FAStatAPI.requestEventStat(orderCode: self.orderCode, shortPlayId: self.shortPlayId, videoId: self.videoId, eventKey: .payError, errorMsg: msg) +// } + + self.completionHandler?(false) + + self.waitRestoreModel = nil + self.clean() + } +} + +extension NRIapManager { + + func getVerifyOrderParameters(orderCode: String, payId: String, transactionId: String, purchaseToken: String) -> [String : Any] { + let parameters: [String : Any] = [ + "order_code" : orderCode, + "pay_setting_id" : payId, + "pkg_name" : kNRAPPBundleIdentifier, + "transaction_id": transactionId, + "purchases_token" : purchaseToken + ] + return parameters + } + + ///预加载支付项 + func preloadingProducts() { + JXIAPManager.manager.fetchReceipt { _ in + self.payRequest = NRPayDataRequest() + self.payRequest?.requestProducts(isLoding: false, isToast: false) { model in + + } + } + } + +} + +extension NRIapManager { + ///成功购买会员 + @objc static let buyVipFinishNotification = Notification.Name(rawValue: "NRIapManager.buyVipFinishNotification") + +} + diff --git a/ReaderHive/Libs/IAP/NRPayDataRequest.swift b/ReaderHive/Libs/IAP/NRPayDataRequest.swift new file mode 100644 index 0000000..d8cf123 --- /dev/null +++ b/ReaderHive/Libs/IAP/NRPayDataRequest.swift @@ -0,0 +1,179 @@ +// +// NRPayDataRequest.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import StoreKit + +class NRPayDataRequest: NSObject { + + private var oldTemplateModel: NRPayDateModel? + private(set) var newTemplateModel: NRPayDateModel? + +// private var payAlertModel: NRPayAlertModel? + + private var completerBlock: ((_ model: NRPayDateModel?) -> Void)? +// private var payAlertBlock: ((_ model: FAPayAlertModel?) -> Void)? + + private var isLoding = false + private var isToast = false + + + func requestProducts(isLoding: Bool = false, isToast: Bool = true, completer: @escaping ((_ model: NRPayDateModel?) -> Void)) { +// self.payAlertBlock = nil + self.completerBlock = completer + self.isLoding = isLoding + self.isToast = isToast + + if isLoding { NRHud.show() } + + NRStoreAPI.requestPayTemplate(isToast: isToast) { [weak self] model in + guard let self = self else { return } + guard let model = model else { + if isLoding { NRHud.dismiss() } + self.completerBlock?(nil) + return + } + + self.oldTemplateModel = model + + var productIdArr: [String] = [] + model.list_sub_vip?.forEach { item in + productIdArr.append(NRIapManager.manager.getProductId(templateId: item.ios_template_id) ?? "") + } + model.list_coins?.forEach { item in + productIdArr.append(NRIapManager.manager.getProductId(templateId: item.ios_template_id) ?? "") + } + + let set = Set(productIdArr) + let productsRequest = SKProductsRequest(productIdentifiers: set) + productsRequest.delegate = self + productsRequest.start() + + } + } + +// func requestCoinAlertData(completer: @escaping ((_ model: FAPayAlertModel?) -> Void)) { +// self.completerBlock = nil +// self.payAlertBlock = completer + +// BRStoreAPI.requestCoinAlertInfo { [weak self] model in +// guard let self = self else { return } +// +// guard let model = model else { +// self.payAlertBlock?(nil) +// return +// } +// self.payAlertModel = model +// +// let productId = BRIAP.manager.getProductId(templateId: model.info?.ios_template_id) ?? "" +// +// let set = Set([productId]) +// let productsRequest = SKProductsRequest(productIdentifiers: set) +// productsRequest.delegate = self +// productsRequest.start() +// } + +// } + + ///挽留信息 +// func requestVipRetainPayInfo(completer: ((_ model: FAPayAlertModel?) -> Void)?) { +// self.completerBlock = nil +// self.payAlertBlock = completer +// +// FAStoreAPI.requestVipRetainPayInfo { [weak self] model in +// guard let self = self else { return } +// guard let model = model else { +// self.payAlertBlock?(nil) +// return +// } +// self.payAlertModel = model +// +// let productId = FAIapManager.manager.getProductId(templateId: model.info?.ios_template_id) ?? "" +// +// let set = Set([productId]) +// let productsRequest = SKProductsRequest(productIdentifiers: set) +// productsRequest.delegate = self +// productsRequest.start() +// } +// } +} + +//MARK: SKProductsRequestDelegate +extension NRPayDataRequest: SKProductsRequestDelegate { + + func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { + if isLoding { + NRHud.dismiss() + } + let products = response.products + + if let block = self.completerBlock { + guard let templateModel = self.oldTemplateModel else { return } + + var newCoinList: [NRPayItem] = [] + var newVipList: [NRPayItem] = [] + + templateModel.list_coins?.forEach { item in + let productId = NRIapManager.manager.getProductId(templateId: item.ios_template_id) ?? "" + for product in products { + if productId == product.productIdentifier { + item.price = product.price.stringValue + item.currency = product.priceLocale.currencySymbol + item.product = product + newCoinList.append(item) + break + } + } + } + + templateModel.list_sub_vip?.forEach { item in + let productId = NRIapManager.manager.getProductId(templateId: item.ios_template_id) ?? "" + for product in products { + if productId == product.productIdentifier { + item.price = product.price.stringValue + item.currency = product.priceLocale.currencySymbol + item.product = product + newVipList.append(item) + break + } + } + } + + + + templateModel.list_coins = newCoinList + templateModel.list_sub_vip = newVipList + + self.newTemplateModel = templateModel + NRIapManager.manager.payDateModel = templateModel + + DispatchQueue.main.async { + block(templateModel) + } + + } +// else if let block = self.payAlertBlock { +// guard let coinalertModel = self.payAlertModel else { return } +// let productId = FAIapManager.manager.getProductId(templateId: coinalertModel.info?.ios_template_id) ?? "" +// +// for product in products { +// if productId == product.productIdentifier { +// coinalertModel.info?.price = product.price.stringValue +// coinalertModel.info?.currency = product.priceLocale.currencySymbol +// coinalertModel.info?.product = product +// break +// } +// } +// +// DispatchQueue.main.async { +// block(coinalertModel) +// } +// } + + } +} + diff --git a/ReaderHive/Libs/IAP/NRWaitRestoreModel.swift b/ReaderHive/Libs/IAP/NRWaitRestoreModel.swift new file mode 100644 index 0000000..3dc9612 --- /dev/null +++ b/ReaderHive/Libs/IAP/NRWaitRestoreModel.swift @@ -0,0 +1,48 @@ +// +// NRWaitRestoreModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit + +class NRWaitRestoreModel: NSObject, NSSecureCoding{ + + var orderCode: String? + var buyType: NRStoreAPI.BuyType? + var payId: String? + var productId: String? + var transactionId: String? + var receipt: String? + + + required override init() { } + + static var supportsSecureCoding: Bool { + get { + return true + } + } + + func encode(with coder: NSCoder) { + coder.encode(orderCode, forKey: "orderCode") + coder.encode(payId, forKey: "payId") + coder.encode(productId, forKey: "productId") + coder.encode(receipt, forKey: "receipt") + coder.encode(buyType?.rawValue, forKey: "buyType") + coder.encode(transactionId, forKey: "transactionId") + } + + required init?(coder: NSCoder) { + super.init() + orderCode = coder.decodeObject(of: NSString.self, forKey: "orderCode") as? String + payId = coder.decodeObject(of: NSString.self, forKey: "payId") as? String + productId = coder.decodeObject(of: NSString.self, forKey: "productId") as? String + receipt = coder.decodeObject(of: NSString.self, forKey: "receipt") as? String + transactionId = coder.decodeObject(of: NSString.self, forKey: "transactionId") as? String + if let type = coder.decodeObject(of: NSString.self, forKey: "buyType") as? String { + buyType = NRStoreAPI.BuyType(rawValue: type) + } + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_07.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_07.imageset/Contents.json new file mode 100644 index 0000000..397060f --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_07.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "arrow@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "arrow@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_07.imageset/arrow@2x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_07.imageset/arrow@2x.png new file mode 100644 index 0000000..eb47355 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_07.imageset/arrow@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_07.imageset/arrow@3x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_07.imageset/arrow@3x.png new file mode 100644 index 0000000..dc1d8d0 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_07.imageset/arrow@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/feedback_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/feedback_icon_01.imageset/Contents.json new file mode 100644 index 0000000..414422f --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/feedback_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "My Wallet@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "My Wallet@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/feedback_icon_01.imageset/My Wallet@2x.png b/ReaderHive/Source/Assets.xcassets/Image/feedback_icon_01.imageset/My Wallet@2x.png new file mode 100644 index 0000000..dd53605 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/feedback_icon_01.imageset/My Wallet@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/feedback_icon_01.imageset/My Wallet@3x.png b/ReaderHive/Source/Assets.xcassets/Image/feedback_icon_01.imageset/My Wallet@3x.png new file mode 100644 index 0000000..58ab4a9 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/feedback_icon_01.imageset/My Wallet@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/gift_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/gift_icon_01.imageset/Contents.json new file mode 100644 index 0000000..ce63af9 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/gift_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Gift@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Gift@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/gift_icon_01.imageset/Gift@2x.png b/ReaderHive/Source/Assets.xcassets/Image/gift_icon_01.imageset/Gift@2x.png new file mode 100644 index 0000000..07158c2 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/gift_icon_01.imageset/Gift@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/gift_icon_01.imageset/Gift@3x.png b/ReaderHive/Source/Assets.xcassets/Image/gift_icon_01.imageset/Gift@3x.png new file mode 100644 index 0000000..b409367 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/gift_icon_01.imageset/Gift@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/me_pack_bg_image.imageset/Banner-DailyReward@2x.png b/ReaderHive/Source/Assets.xcassets/Image/me_pack_bg_image.imageset/Banner-DailyReward@2x.png new file mode 100644 index 0000000..fbd2391 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/me_pack_bg_image.imageset/Banner-DailyReward@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/me_pack_bg_image.imageset/Banner-DailyReward@3x.png b/ReaderHive/Source/Assets.xcassets/Image/me_pack_bg_image.imageset/Banner-DailyReward@3x.png new file mode 100644 index 0000000..9329048 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/me_pack_bg_image.imageset/Banner-DailyReward@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/me_pack_bg_image.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/me_pack_bg_image.imageset/Contents.json new file mode 100644 index 0000000..04a6be8 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/me_pack_bg_image.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Banner-DailyReward@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Banner-DailyReward@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/time_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/time_icon_01.imageset/Contents.json new file mode 100644 index 0000000..6a4d508 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/time_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Vector@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Vector@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/time_icon_01.imageset/Vector@2x.png b/ReaderHive/Source/Assets.xcassets/Image/time_icon_01.imageset/Vector@2x.png new file mode 100644 index 0000000..e85d70a Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/time_icon_01.imageset/Vector@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/time_icon_01.imageset/Vector@3x.png b/ReaderHive/Source/Assets.xcassets/Image/time_icon_01.imageset/Vector@3x.png new file mode 100644 index 0000000..7a48914 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/time_icon_01.imageset/Vector@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/time_icon_02.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/time_icon_02.imageset/Contents.json new file mode 100644 index 0000000..6efec3a --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/time_icon_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "time icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "time icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/time_icon_02.imageset/time icon@2x.png b/ReaderHive/Source/Assets.xcassets/Image/time_icon_02.imageset/time icon@2x.png new file mode 100644 index 0000000..20cb186 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/time_icon_02.imageset/time icon@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/time_icon_02.imageset/time icon@3x.png b/ReaderHive/Source/Assets.xcassets/Image/time_icon_02.imageset/time icon@3x.png new file mode 100644 index 0000000..0a63018 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/time_icon_02.imageset/time icon@3x.png differ diff --git a/ReaderHive/Source/en.lproj/Localizable.strings b/ReaderHive/Source/en.lproj/Localizable.strings index 411e747..5816cab 100644 --- a/ReaderHive/Source/en.lproj/Localizable.strings +++ b/ReaderHive/Source/en.lproj/Localizable.strings @@ -83,8 +83,36 @@ "Top Up" = "Top Up"; "Language" = "Language"; "Loading" = "Loading"; +"Store" = "Store"; +"Coins Purchase" = "Coins Purchase"; +"VIP Membership" = "VIP Membership"; +"Auto renew,cancel anytime" = "Auto renew,cancel anytime"; +"Feedback" = "Feedback"; +"Feedback History" = "Feedback History"; +"Feedback Details" = "Feedback Details"; +"Account Deletion" = "Account Deletion"; +"me_coins_pack_title" = "Daily reward ready!"; +"me_coins_pack_subtitle" = "Claim your rewards now."; +"Weekly VIP" = "Weekly VIP"; +"My Wallet" = "My Wallet"; +"Consumption Records" = "Consumption Records"; +"Purchase Records" = "Purchase Records"; +"Reward Coins" = "Reward Coins"; +"Purchase Single Episode" = "Purchase Single Episode"; +"Expired" = "Expired"; +"Expires in ## days" = "Expires in ## days"; +"Order Records" = "Order Records"; +"Coin Record" = "Coin Record"; +"VIP Record" = "VIP Record"; +"pay_error_1" = "You are already a member!"; +"pay_error_2" = "You have unfinished in-app purchases, please restore them first."; +"pay_error_3" = "There are no recoverable in-app purchases."; +"pay_error_4" = "Purchase Failed"; +"pay_error_5" = "Invalid in-app purchase"; +"pay_error_6" = "Payment has been cancelled"; "network_error_1" = "Your account is already logged in on another device~"; "network_error_2" = "The service is abnormal. Check the network."; + diff --git a/ReaderHive/Thirdparty/JXIAPManager/JXIAPManager.swift b/ReaderHive/Thirdparty/JXIAPManager/JXIAPManager.swift new file mode 100644 index 0000000..0f593ee --- /dev/null +++ b/ReaderHive/Thirdparty/JXIAPManager/JXIAPManager.swift @@ -0,0 +1,223 @@ +// +// JXIAPManager.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/10. +// + +import UIKit +import StoreKit + +@objc protocol JXIAPManagerDelegate { + /// 购买成功 + @objc optional func jx_iapPaySuccess(productId: String, receipt: String, transactionIdentifier: String) + /// 购买失败 + @objc optional func jx_iapPayFailed(productId: String, code: JXIAPManagerCode, msg: String?) + /// 恢复商品(仅限永久有效商品) + @objc optional func iapPayRestore(productIds: [String], transactionIds: [String]) +// /// 加载 +// @objc optional func iapPayShowHud() +// /// 系统错误 +// @objc optional func iapSysWrong() +// /// 验证成功 +// @objc optional func verifySuccess() +// /// 验证失败 +// @objc optional func verifyFailed() +} + +@objc enum JXIAPManagerCode: Int { + ///未知错误 + case unknown + ///取消交易 + case cancelled + ///没有商品 + case noProduct +} + +class JXIAPManager: NSObject { + + + static let manager: JXIAPManager = JXIAPManager() + + weak var delegate: JXIAPManagerDelegate? + + private var payment: SKPayment? + + private var product: SKProduct? + private var productId: String? + + private var discount: SKPaymentDiscount? + private var orderId: String? + private var applicationUsername: String? + + private var receiptCompletion: ((URL?) -> Void)? + + override init() { + super.init() + SKPaymentQueue.default().add(self) + } + + + func start(productId: String, orderId: String, applicationUsername: String?, discount: SKPaymentDiscount? = nil) { + self.product = nil + self.productId = productId + self.orderId = orderId + self.discount = discount + self.applicationUsername = applicationUsername + + let set = Set([productId]) + let productsRequest = SKProductsRequest(productIdentifiers: set) + productsRequest.delegate = self + productsRequest.start() + } + + /// 购买商品 + private func buyProduct() { + guard let product = self.product else { return } + + // 要购买商品,开个小票 + let payment = SKMutablePayment(product: product) + payment.applicationUsername = self.applicationUsername + if let discount = self.discount { + payment.paymentDiscount = discount + self.discount = nil + } + + self.payment = payment + + // 去收银台排队,准备购买 + SKPaymentQueue.default().add(payment) + } + +} + +//MARK: -------------- SKProductsRequestDelegate -------------- +extension JXIAPManager: SKProductsRequestDelegate { + func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { + guard let product = response.products.first else { + DispatchQueue.main.async { + if let productId = self.productId { + self.productId = nil + self.delegate?.jx_iapPayFailed?(productId: productId, code: .noProduct, msg: nil) + } + } + return + } + self.product = product + + self.buyProduct() + + } +} + +//MARK: -------------- SKPaymentTransactionObserver -------------- +extension JXIAPManager: SKPaymentTransactionObserver { + ///购买回调 + func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { + + for transaction in transactions { + switch transaction.transactionState { + case .purchased: + DispatchQueue.main.async { + self.completeTransaction(transaction: transaction) + } + SKPaymentQueue.default().finishTransaction(transaction) + + case .failed: + DispatchQueue.main.async { + self.failedTransaction(transaction: transaction) + } + SKPaymentQueue.default().finishTransaction(transaction) +// case .restored: +// self.restoreTransaction(transaction: transaction) + + case .purchasing: + break + default: + SKPaymentQueue.default().finishTransaction(transaction) + break + } + + + } + } + +// func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool { +// return true +// } + + /// 恢复购买回调 + func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { + + } + +} + +extension JXIAPManager { + + + private func completeTransaction(transaction: SKPaymentTransaction) { + guard let encodeStr = getAppStoreReceipt() else { return } + guard let transactionIdentifier = transaction.transactionIdentifier else { return } + + guard let productId = self.productId, productId == transaction.payment.productIdentifier else { return } + self.productId = nil + self.delegate?.jx_iapPaySuccess?(productId: productId, receipt: encodeStr, transactionIdentifier: transactionIdentifier) + + } + + private func failedTransaction(transaction: SKPaymentTransaction) { + let error = transaction.error as? SKError + guard let productId = self.productId else { return } + self.productId = nil + + switch error?.code { + case SKError.paymentCancelled: + self.delegate?.jx_iapPayFailed?(productId: productId, code: .cancelled, msg: error?.localizedDescription) + default: + self.delegate?.jx_iapPayFailed?(productId: productId, code: .unknown, msg: error?.localizedDescription) + } + + } + + + +} + +extension JXIAPManager: SKRequestDelegate { + + func getAppStoreReceipt() -> String? { + guard let receiptURL = Bundle.main.appStoreReceiptURL else { return nil } + let receiptData = NSData(contentsOf: receiptURL) + return receiptData?.base64EncodedString(options: .endLineWithLineFeed) + } + + ///获取凭证 + func fetchReceipt(completion: @escaping (URL?) -> Void) { + let receiptURL = Bundle.main.appStoreReceiptURL + if let url = receiptURL, FileManager.default.fileExists(atPath: url.path) { + completion(url) + return + } + + let request = SKReceiptRefreshRequest() + request.delegate = self + request.start() + // 在 delegate 回调里处理 + self.receiptCompletion = completion + } + + func requestDidFinish(_ request: SKRequest) { + let receiptURL = Bundle.main.appStoreReceiptURL + if let url = receiptURL, FileManager.default.fileExists(atPath: url.path) { + receiptCompletion?(url) + } else { + receiptCompletion?(nil) + } + } + + func request(_ request: SKRequest, didFailWithError error: Error) { + print("Receipt request failed: \(error)") + receiptCompletion?(nil) + } +}