个人主页开发
@ -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 = "<group>"; };
|
||||
F3B8593F2EE6787E0095A9CC /* NRLocalizedManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRLocalizedManager.swift; sourceTree = "<group>"; };
|
||||
F3B859412EE678FB0095A9CC /* NRSettingAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRSettingAPI.swift; sourceTree = "<group>"; };
|
||||
F3B859432EE902BF0095A9CC /* NRStoreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreViewController.swift; sourceTree = "<group>"; };
|
||||
F3B8594B2EE904980095A9CC /* NRPayDateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRPayDateModel.swift; sourceTree = "<group>"; };
|
||||
F3B8594D2EE905A40095A9CC /* NRStoreAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreAPI.swift; sourceTree = "<group>"; };
|
||||
F3B859512EE906A80095A9CC /* JXIAPManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JXIAPManager.swift; sourceTree = "<group>"; };
|
||||
F3B859562EE907280095A9CC /* NRIapManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRIapManager.swift; sourceTree = "<group>"; };
|
||||
F3B859582EE907350095A9CC /* NRIAPOrderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRIAPOrderModel.swift; sourceTree = "<group>"; };
|
||||
F3B8595A2EE907600095A9CC /* NRWaitRestoreModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRWaitRestoreModel.swift; sourceTree = "<group>"; };
|
||||
F3B8595C2EE907710095A9CC /* NRIAPVerifyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRIAPVerifyModel.swift; sourceTree = "<group>"; };
|
||||
F3B8595E2EE910020095A9CC /* NRPayDataRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRPayDataRequest.swift; sourceTree = "<group>"; };
|
||||
F3B859602EE9126A0095A9CC /* Dictionary+NRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+NRAdd.swift"; sourceTree = "<group>"; };
|
||||
F3B859622EE91B850095A9CC /* NRStoreCoinsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreCoinsView.swift; sourceTree = "<group>"; };
|
||||
F3B859642EE91BB70095A9CC /* NRStoreCoinsBigCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreCoinsBigCell.swift; sourceTree = "<group>"; };
|
||||
F3B859662EE91BC50095A9CC /* NRStoreCoinsSmallCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreCoinsSmallCell.swift; sourceTree = "<group>"; };
|
||||
F3B859682EE91BD70095A9CC /* NRStoreCoinsPackCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreCoinsPackCell.swift; sourceTree = "<group>"; };
|
||||
F3B8596A2EE91C9F0095A9CC /* NRStoreCoinsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreCoinsCell.swift; sourceTree = "<group>"; };
|
||||
F3B8596C2EE944DE0095A9CC /* NRStoreVipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreVipView.swift; sourceTree = "<group>"; };
|
||||
F3B8596E2EE9456E0095A9CC /* NRStoreVipCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStoreVipCell.swift; sourceTree = "<group>"; };
|
||||
F3B859702EE94A1B0095A9CC /* NRFeedbackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRFeedbackViewController.swift; sourceTree = "<group>"; };
|
||||
F3B859722EE94A760095A9CC /* NRAppWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRAppWebViewController.swift; sourceTree = "<group>"; };
|
||||
F3B859742EE9515B0095A9CC /* NRMeCoinsPackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRMeCoinsPackView.swift; sourceTree = "<group>"; };
|
||||
F3B859762EE95B220095A9CC /* NRMeVipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRMeVipView.swift; sourceTree = "<group>"; };
|
||||
F3B859782EE960D20095A9CC /* NRWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRWalletViewController.swift; sourceTree = "<group>"; };
|
||||
F3B8597A2EE961EF0095A9CC /* NRWalletCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRWalletCell.swift; sourceTree = "<group>"; };
|
||||
F3B8597C2EE9627B0095A9CC /* NRWalletHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRWalletHeaderView.swift; sourceTree = "<group>"; };
|
||||
F3B8597E2EE96F810095A9CC /* NRConsumptionRecordsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRConsumptionRecordsViewController.swift; sourceTree = "<group>"; };
|
||||
F3B859802EE9716E0095A9CC /* NRBuyRecordsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRBuyRecordsModel.swift; sourceTree = "<group>"; };
|
||||
F3B859842EE972F70095A9CC /* NRConsumptionRecordsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRConsumptionRecordsCell.swift; sourceTree = "<group>"; };
|
||||
F3B859852EE972F70095A9CC /* NRConsumptionRecordsCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NRConsumptionRecordsCell.xib; sourceTree = "<group>"; };
|
||||
F3B859882EE97E1F0095A9CC /* NRRewardCoinsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRRewardCoinsViewController.swift; sourceTree = "<group>"; };
|
||||
F3B8598A2EEA51540095A9CC /* NRRewardCoinsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRRewardCoinsModel.swift; sourceTree = "<group>"; };
|
||||
F3B8598C2EEA51FE0095A9CC /* NRRewardCoinsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRRewardCoinsCell.swift; sourceTree = "<group>"; };
|
||||
F3B8598E2EEA5B1C0095A9CC /* NROrderRecordsPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NROrderRecordsPageViewController.swift; sourceTree = "<group>"; };
|
||||
F3B859902EEA627F0095A9CC /* NROrderRecordsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NROrderRecordsViewController.swift; sourceTree = "<group>"; };
|
||||
F3B859922EEA63CD0095A9CC /* NROrderRecordsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NROrderRecordsModel.swift; sourceTree = "<group>"; };
|
||||
F3B859942EEA64430095A9CC /* NROrderRecordsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NROrderRecordsCell.swift; sourceTree = "<group>"; };
|
||||
/* 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 = "<group>";
|
||||
@ -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 = "<group>";
|
||||
@ -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 = "<group>";
|
||||
@ -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 = "<group>";
|
||||
};
|
||||
F3B859452EE904390095A9CC /* Store */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3B8594A2EE9045C0095A9CC /* VC */,
|
||||
F3B859482EE9044F0095A9CC /* V */,
|
||||
F3B859492EE904580095A9CC /* M */,
|
||||
);
|
||||
path = Store;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
F3B859492EE904580095A9CC /* M */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3B8594B2EE904980095A9CC /* NRPayDateModel.swift */,
|
||||
F3B859802EE9716E0095A9CC /* NRBuyRecordsModel.swift */,
|
||||
F3B8598A2EEA51540095A9CC /* NRRewardCoinsModel.swift */,
|
||||
F3B859922EEA63CD0095A9CC /* NROrderRecordsModel.swift */,
|
||||
);
|
||||
path = M;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
F3B8594F2EE9068E0095A9CC /* Thirdparty */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3B859502EE906A40095A9CC /* JXIAPManager */,
|
||||
);
|
||||
path = Thirdparty;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F3B859502EE906A40095A9CC /* JXIAPManager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3B859512EE906A80095A9CC /* JXIAPManager.swift */,
|
||||
);
|
||||
path = JXIAPManager;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F3B859552EE907220095A9CC /* IAP */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3B859562EE907280095A9CC /* NRIapManager.swift */,
|
||||
F3B8595E2EE910020095A9CC /* NRPayDataRequest.swift */,
|
||||
F3B859582EE907350095A9CC /* NRIAPOrderModel.swift */,
|
||||
F3B8595C2EE907710095A9CC /* NRIAPVerifyModel.swift */,
|
||||
F3B8595A2EE907600095A9CC /* NRWaitRestoreModel.swift */,
|
||||
);
|
||||
path = IAP;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* 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;
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -11,3 +11,5 @@ let kNRLoginTokenDefaultsKey = "kNRLoginTokenDefaultsKey"
|
||||
let kNRUserInfoDefaultsKey = "kNRUserInfoDefaultsKey"
|
||||
///阅读设置
|
||||
let kNRNovelReadSetDefaultsKey = "kNRNovelReadSetDefaultsKey"
|
||||
|
||||
let kNRWaitRestoreIAPDefaultsKey = "kNRWaitRestoreIAPDefaultsKey"
|
||||
|
||||
23
ReaderHive/Base/Extension/Dictionary+NRAdd.swift
Normal file
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
130
ReaderHive/Base/Networking/API/NRStoreAPI.swift
Normal file
@ -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<NRPayDateModel>) 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<NRIAPOrderModel>) 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<NRIAPVerifyModel>) -> Void)?) {
|
||||
var param = NRNetwork.Parameters(path: "/applePaid")
|
||||
param.parameters = parameters
|
||||
|
||||
NRNetwork.request(parameters: param) { (response: NRNetwork.Response<NRIAPVerifyModel>) in
|
||||
completer?(response)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static func requestBuyRecords(page: Int, completer: ((_ listModel: NRNetwork.List<NRBuyRecordsModel>?) -> 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<NRNetwork.List<NRBuyRecordsModel>>) in
|
||||
completer?(response.data)
|
||||
}
|
||||
}
|
||||
|
||||
static func requestRechargeRecord(page: Int, buyType: BuyType, completer: ((_ listModel: NRNetwork.List<NROrderRecordsModel>?) -> 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<NRNetwork.List<NROrderRecordsModel>>) in
|
||||
completer?(response.data)
|
||||
}
|
||||
}
|
||||
|
||||
static func reuqestSendCoinRecord(page: Int, completer: ((_ listModel: NRNetwork.List<NRRewardCoinsModel>?) -> 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<NRNetwork.List<NRRewardCoinsModel>>) in
|
||||
completer?(response.data)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
78
ReaderHive/Base/WebView/NRAppWebViewController.swift
Normal file
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@preconcurrency import WebKit
|
||||
internal import WebKit
|
||||
import YYText
|
||||
|
||||
//MARK:-------------- VPWebViewDelegate --------------
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import WebKit
|
||||
internal import WebKit
|
||||
|
||||
///APP交互
|
||||
let kNRWebMessageAPP = "js2app"
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import WebKit
|
||||
internal import WebKit
|
||||
import SnapKit
|
||||
|
||||
class NRWebViewController: NRViewController {
|
||||
|
||||
@ -15,6 +15,10 @@ struct NRMeItem {
|
||||
case about
|
||||
case settings
|
||||
case web
|
||||
case feedback
|
||||
case consumptionRecords
|
||||
case purchaseRecords
|
||||
case rewardCoins
|
||||
}
|
||||
|
||||
var type: ItemType
|
||||
|
||||
@ -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)
|
||||
|
||||
95
ReaderHive/Class/Me/V/NRMeCoinsPackView.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
92
ReaderHive/Class/Me/V/NRMeVipView.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
30
ReaderHive/Class/Me/VC/NRFeedbackViewController.swift
Normal file
@ -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.
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
23
ReaderHive/Class/Store/M/NRBuyRecordsModel.swift
Normal file
@ -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?
|
||||
}
|
||||
18
ReaderHive/Class/Store/M/NROrderRecordsModel.swift
Normal file
@ -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?
|
||||
|
||||
}
|
||||
156
ReaderHive/Class/Store/M/NRPayDateModel.swift
Normal file
@ -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]?
|
||||
|
||||
}
|
||||
23
ReaderHive/Class/Store/M/NRRewardCoinsModel.swift
Normal file
@ -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?
|
||||
}
|
||||
41
ReaderHive/Class/Store/V/NRConsumptionRecordsCell.swift
Normal file
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
77
ReaderHive/Class/Store/V/NRConsumptionRecordsCell.xib
Normal file
@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="78" id="mEn-bQ-axV" customClass="NRConsumptionRecordsCell" customModule="ReaderHive" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="415" height="78"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="mEn-bQ-axV" id="X0I-th-PNb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="415" height="78"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="opT-kU-oJu">
|
||||
<rect key="frame" x="28" y="16" width="36" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
|
||||
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DEl-sR-gIq">
|
||||
<rect key="frame" x="28" y="47" width="31" height="15"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<color key="textColor" name="#999999"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="1000" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZrS-ca-7Sl">
|
||||
<rect key="frame" x="356" y="17.666666666666668" width="31" height="14"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<color key="textColor" name="#999999"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="1000" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1q9-82-w0e">
|
||||
<rect key="frame" x="351" y="46" width="36" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
|
||||
<color key="textColor" name="#F9710D"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="1q9-82-w0e" secondAttribute="trailing" constant="28" id="5o1-UO-s6F"/>
|
||||
<constraint firstItem="ZrS-ca-7Sl" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="opT-kU-oJu" secondAttribute="trailing" constant="30" id="7pK-zi-HYk"/>
|
||||
<constraint firstItem="1q9-82-w0e" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="DEl-sR-gIq" secondAttribute="trailing" constant="60" id="Dpw-QY-iFP"/>
|
||||
<constraint firstAttribute="bottom" secondItem="DEl-sR-gIq" secondAttribute="bottom" constant="16" id="IxU-Af-tWS"/>
|
||||
<constraint firstItem="DEl-sR-gIq" firstAttribute="leading" secondItem="X0I-th-PNb" secondAttribute="leading" constant="28" id="O8d-8Q-5SU"/>
|
||||
<constraint firstItem="ZrS-ca-7Sl" firstAttribute="centerY" secondItem="opT-kU-oJu" secondAttribute="centerY" id="RXQ-Uw-07h"/>
|
||||
<constraint firstItem="opT-kU-oJu" firstAttribute="top" secondItem="X0I-th-PNb" secondAttribute="top" constant="16" id="aK0-PR-3qy"/>
|
||||
<constraint firstItem="opT-kU-oJu" firstAttribute="leading" secondItem="X0I-th-PNb" secondAttribute="leading" constant="28" id="at7-xM-Ouk"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ZrS-ca-7Sl" secondAttribute="trailing" constant="28" id="cj8-ih-wWV"/>
|
||||
<constraint firstItem="1q9-82-w0e" firstAttribute="centerY" secondItem="DEl-sR-gIq" secondAttribute="centerY" id="xIx-uT-BHf"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<viewLayoutGuide key="safeArea" id="SI6-uw-Qde"/>
|
||||
<connections>
|
||||
<outlet property="coinsLabel" destination="1q9-82-w0e" id="Txn-S2-YmX"/>
|
||||
<outlet property="subtitleLabel" destination="DEl-sR-gIq" id="9KO-QD-Mwx"/>
|
||||
<outlet property="timeLabel" destination="ZrS-ca-7Sl" id="dKo-E4-twa"/>
|
||||
<outlet property="titleLabel" destination="opT-kU-oJu" id="HAF-dh-KMA"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="571.75572519083971" y="53.521126760563384"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<namedColor name="#999999">
|
||||
<color red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<namedColor name="#F9710D">
|
||||
<color red="0.97647058823529409" green="0.44313725490196076" blue="0.050980392156862744" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
</resources>
|
||||
</document>
|
||||
68
ReaderHive/Class/Store/V/NROrderRecordsCell.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
119
ReaderHive/Class/Store/V/NRRewardCoinsCell.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
12
ReaderHive/Class/Store/V/NRStoreCoinsBigCell.swift
Normal file
@ -0,0 +1,12 @@
|
||||
//
|
||||
// NRStoreCoinsBigCell.swift
|
||||
// ReaderHive
|
||||
//
|
||||
// Created by 长沙鸿瑶 on 2025/12/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class NRStoreCoinsBigCell: NRStoreCoinsCell {
|
||||
|
||||
}
|
||||
19
ReaderHive/Class/Store/V/NRStoreCoinsCell.swift
Normal file
@ -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
|
||||
}
|
||||
12
ReaderHive/Class/Store/V/NRStoreCoinsPackCell.swift
Normal file
@ -0,0 +1,12 @@
|
||||
//
|
||||
// NRStoreCoinsPackCell.swift
|
||||
// ReaderHive
|
||||
//
|
||||
// Created by 长沙鸿瑶 on 2025/12/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class NRStoreCoinsPackCell: NRStoreCoinsCell {
|
||||
|
||||
}
|
||||
12
ReaderHive/Class/Store/V/NRStoreCoinsSmallCell.swift
Normal file
@ -0,0 +1,12 @@
|
||||
//
|
||||
// NRStoreCoinsSmallCell.swift
|
||||
// ReaderHive
|
||||
//
|
||||
// Created by 长沙鸿瑶 on 2025/12/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class NRStoreCoinsSmallCell: NRStoreCoinsCell {
|
||||
|
||||
}
|
||||
253
ReaderHive/Class/Store/V/NRStoreCoinsView.swift
Normal file
@ -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?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
16
ReaderHive/Class/Store/V/NRStoreVipCell.swift
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// NRStoreVipCell.swift
|
||||
// ReaderHive
|
||||
//
|
||||
// Created by 长沙鸿瑶 on 2025/12/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class NRStoreVipCell: UICollectionViewCell {
|
||||
var model: NRPayItem? {
|
||||
didSet {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
166
ReaderHive/Class/Store/V/NRStoreVipView.swift
Normal file
@ -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?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
49
ReaderHive/Class/Store/V/NRWalletCell.swift
Normal file
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
186
ReaderHive/Class/Store/V/NRWalletHeaderView.swift
Normal file
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -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?()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
118
ReaderHive/Class/Store/VC/NROrderRecordsPageViewController.swift
Normal file
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
108
ReaderHive/Class/Store/VC/NROrderRecordsViewController.swift
Normal file
@ -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?()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
107
ReaderHive/Class/Store/VC/NRRewardCoinsViewController.swift
Normal file
@ -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?()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
82
ReaderHive/Class/Store/VC/NRStoreViewController.swift
Normal file
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
114
ReaderHive/Class/Store/VC/NRWalletViewController.swift
Normal file
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
41
ReaderHive/Libs/IAP/NRIAPOrderModel.swift
Normal file
@ -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?
|
||||
}
|
||||
19
ReaderHive/Libs/IAP/NRIAPVerifyModel.swift
Normal file
@ -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?
|
||||
}
|
||||
293
ReaderHive/Libs/IAP/NRIapManager.swift
Normal file
@ -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")
|
||||
|
||||
}
|
||||
|
||||
179
ReaderHive/Libs/IAP/NRPayDataRequest.swift
Normal file
@ -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)
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
48
ReaderHive/Libs/IAP/NRWaitRestoreModel.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
22
ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_07.imageset/Contents.json
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
BIN
ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_07.imageset/arrow@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 233 B |
BIN
ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_07.imageset/arrow@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 330 B |
22
ReaderHive/Source/Assets.xcassets/Image/feedback_icon_01.imageset/Contents.json
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
BIN
ReaderHive/Source/Assets.xcassets/Image/feedback_icon_01.imageset/My Wallet@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
ReaderHive/Source/Assets.xcassets/Image/feedback_icon_01.imageset/My Wallet@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
22
ReaderHive/Source/Assets.xcassets/Image/gift_icon_01.imageset/Contents.json
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
BIN
ReaderHive/Source/Assets.xcassets/Image/gift_icon_01.imageset/Gift@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
ReaderHive/Source/Assets.xcassets/Image/gift_icon_01.imageset/Gift@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
ReaderHive/Source/Assets.xcassets/Image/me_pack_bg_image.imageset/Banner-DailyReward@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
ReaderHive/Source/Assets.xcassets/Image/me_pack_bg_image.imageset/Banner-DailyReward@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 132 KiB |
22
ReaderHive/Source/Assets.xcassets/Image/me_pack_bg_image.imageset/Contents.json
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
22
ReaderHive/Source/Assets.xcassets/Image/time_icon_01.imageset/Contents.json
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
BIN
ReaderHive/Source/Assets.xcassets/Image/time_icon_01.imageset/Vector@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 406 B |
BIN
ReaderHive/Source/Assets.xcassets/Image/time_icon_01.imageset/Vector@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 554 B |
22
ReaderHive/Source/Assets.xcassets/Image/time_icon_02.imageset/Contents.json
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
BIN
ReaderHive/Source/Assets.xcassets/Image/time_icon_02.imageset/time icon@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 790 B |
BIN
ReaderHive/Source/Assets.xcassets/Image/time_icon_02.imageset/time icon@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
@ -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.";
|
||||
|
||||
|
||||
223
ReaderHive/Thirdparty/JXIAPManager/JXIAPManager.swift
vendored
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||