diff --git a/.gitignore b/.gitignore index 3542e5b..7bfb6c9 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ xcuserdata/ *.ipa *.dSYM.zip *.dSYM +Podfile.lock ## Playgrounds timeline.xctimeline @@ -38,10 +39,10 @@ playground.xcworkspace # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # -# Pods/ +Pods/ # # Add this line if you want to avoid checking in source code from the Xcode workspace -# *.xcworkspace +*.xcworkspace # Carthage # diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..016e1fc --- /dev/null +++ b/Podfile @@ -0,0 +1,37 @@ +# Uncomment the next line to define a global platform for your project + platform :ios, '13.0' + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64" + config.build_settings['EXCLUDED_ARCHITECTURES'] = 'i386' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' + end + end +end + +target 'ReaderHive' do + use_frameworks! + + pod 'Moya' + pod 'SmartCodable', '5.0.15' + pod 'Kingfisher' + pod 'SnapKit' + pod 'YYCategories' + pod 'YYText' + pod 'JXSegmentedView' + pod 'JXPagingView/Paging' + pod 'FSPagerView' + pod 'collection-view-layouts/TagsLayout' + pod 'IQKeyboardManagerSwift' + pod 'LYEmptyView' + pod 'HWPanModal' + pod 'MJRefresh' + pod 'Cosmos' #星星评分 + pod 'Toast' + pod 'SVProgressHUD' + pod 'FDFullscreenPopGesture' + +end diff --git a/ReaderHive.xcodeproj/project.pbxproj b/ReaderHive.xcodeproj/project.pbxproj new file mode 100644 index 0000000..48cf5b1 --- /dev/null +++ b/ReaderHive.xcodeproj/project.pbxproj @@ -0,0 +1,1503 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 0373D9402ED57B1C0017DCC7 /* NRNovelDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D93F2ED57B1C0017DCC7 /* NRNovelDetailViewController.swift */; }; + 0373D9452ED57B7B0017DCC7 /* NRNovelDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D9442ED57B7B0017DCC7 /* NRNovelDetailViewModel.swift */; }; + 0373D9472ED57F3F0017DCC7 /* UINavigationBar+NRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D9462ED57F3A0017DCC7 /* UINavigationBar+NRAdd.swift */; }; + 0373D94B2ED582EE0017DCC7 /* NRNovelAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D94A2ED582E10017DCC7 /* NRNovelAPI.swift */; }; + 0373D94D2ED583A80017DCC7 /* NRNovelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D94C2ED583A80017DCC7 /* NRNovelModel.swift */; }; + 0373D94F2ED58A1E0017DCC7 /* NRSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D94E2ED58A1E0017DCC7 /* NRSearchViewController.swift */; }; + 0373D9522ED58A950017DCC7 /* NRSearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D9512ED58A950017DCC7 /* NRSearchViewModel.swift */; }; + 0373D9542ED58AF00017DCC7 /* NRSearchInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D9532ED58AF00017DCC7 /* NRSearchInputView.swift */; }; + 0373D9562ED5933F0017DCC7 /* NRSearchHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D9552ED5933F0017DCC7 /* NRSearchHomeView.swift */; }; + 0373D9582ED5935D0017DCC7 /* NRSearchRecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D9572ED5935D0017DCC7 /* NRSearchRecordView.swift */; }; + 0373D95A2ED593D50017DCC7 /* NRSearchRecordCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D9592ED593D50017DCC7 /* NRSearchRecordCell.swift */; }; + 0373D95C2ED598890017DCC7 /* UIStackView+NRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D95B2ED598830017DCC7 /* UIStackView+NRAdd.swift */; }; + 0373D95E2ED59C430017DCC7 /* NRSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D95D2ED59C430017DCC7 /* NRSearchResultView.swift */; }; + 0373D9602ED59DA10017DCC7 /* NRSearchResultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D95F2ED59DA10017DCC7 /* NRSearchResultCell.swift */; }; + 0373D9642ED5ABBC0017DCC7 /* NREmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373D9632ED5ABB80017DCC7 /* NREmpty.swift */; }; + 03980F872ED009EB0006E317 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03980F7D2ED009EB0006E317 /* AppDelegate.swift */; }; + 03980F882ED009EB0006E317 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03980F842ED009EB0006E317 /* SceneDelegate.swift */; }; + 03980F8A2ED009EB0006E317 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 03980F7E2ED009EB0006E317 /* Assets.xcassets */; }; + 03980F8C2ED009EB0006E317 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 03980F812ED009EB0006E317 /* LaunchScreen.storyboard */; }; + 0398105C2ED047FE0006E317 /* NRTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0398105B2ED047FE0006E317 /* NRTabBarController.swift */; }; + 0398105E2ED0481E0006E317 /* NRNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0398105D2ED0481E0006E317 /* NRNavigationController.swift */; }; + 039810602ED04D950006E317 /* NRNetworkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0398105F2ED04D950006E317 /* NRNetworkModel.swift */; }; + 039810622ED04F250006E317 /* NRNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810612ED04F250006E317 /* NRNetwork.swift */; }; + 039810642ED04F480006E317 /* NRTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810632ED04F480006E317 /* NRTargetType.swift */; }; + 039810662ED04F940006E317 /* NRUrlPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810652ED04F940006E317 /* NRUrlPath.swift */; }; + 039810682ED050390006E317 /* NRResponseCryptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810672ED050390006E317 /* NRResponseCryptor.swift */; }; + 0398106A2ED0505D0006E317 /* NRNetworkReachableManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810692ED0505D0006E317 /* NRNetworkReachableManager.swift */; }; + 0398106D2ED053000006E317 /* NRDefine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0398106C2ED052FC0006E317 /* NRDefine.swift */; }; + 039810702ED053910006E317 /* String+NRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0398106F2ED053850006E317 /* String+NRAdd.swift */; }; + 039810732ED053BE0006E317 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 039810712ED053BE0006E317 /* Localizable.strings */; }; + 039810782ED054740006E317 /* NRHud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810772ED054740006E317 /* NRHud.swift */; }; + 0398107A2ED054E40006E317 /* NRToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810792ED054E40006E317 /* NRToast.swift */; }; + 0398107C2ED055260006E317 /* AppDelegate+Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0398107B2ED0551C0006E317 /* AppDelegate+Config.swift */; }; + 0398107F2ED055D10006E317 /* NRLoginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0398107E2ED055D10006E317 /* NRLoginManager.swift */; }; + 039810812ED056090006E317 /* NRLoginToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810802ED056090006E317 /* NRLoginToken.swift */; }; + 039810832ED0563D0006E317 /* NRUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810822ED0563D0006E317 /* NRUserInfo.swift */; }; + 039810852ED056D70006E317 /* UserDefaults+NRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810842ED056D00006E317 /* UserDefaults+NRAdd.swift */; }; + 039810872ED057260006E317 /* NRUserDefaultsKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810862ED057210006E317 /* NRUserDefaultsKey.swift */; }; + 0398108A2ED0582F0006E317 /* NRDeviceId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810892ED0582B0006E317 /* NRDeviceId.swift */; }; + 0398108C2ED0584F0006E317 /* NRKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0398108B2ED0584F0006E317 /* NRKeychain.swift */; }; + 0398108E2ED060020006E317 /* UIFont+NRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0398108D2ED05FFA0006E317 /* UIFont+NRAdd.swift */; }; + 039810902ED060EF0006E317 /* NRViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0398108F2ED060EF0006E317 /* NRViewController.swift */; }; + 039810932ED062CE0006E317 /* NRHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810922ED062CE0006E317 /* NRHomeViewController.swift */; }; + 039810952ED066710006E317 /* UIScreen+NRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810942ED066710006E317 /* UIScreen+NRAdd.swift */; }; + 039810982ED066B20006E317 /* NRTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810972ED066B20006E317 /* NRTool.swift */; }; + 0398109B2ED0692A0006E317 /* NRImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0398109A2ED0692A0006E317 /* NRImageView.swift */; }; + 039810A02ED06B7C0006E317 /* NRHomeNovelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0398109F2ED06B7C0006E317 /* NRHomeNovelViewController.swift */; }; + 039810A22ED070400006E317 /* NRHomeNovelHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810A12ED070400006E317 /* NRHomeNovelHeaderView.swift */; }; + 039810A42ED072380006E317 /* NRHomeNovelListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810A32ED072380006E317 /* NRHomeNovelListViewController.swift */; }; + 039810A62ED072820006E317 /* NRCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810A52ED072820006E317 /* NRCollectionView.swift */; }; + 039810A82ED3E7DB0006E317 /* NRHomeNovelListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810A72ED3E7DB0006E317 /* NRHomeNovelListCell.swift */; }; + 039810AA2ED3EC2E0006E317 /* NRScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810A92ED3EC2E0006E317 /* NRScrollView.swift */; }; + 039810AC2ED3EF640006E317 /* NRHomeNovelHeaderContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810AB2ED3EF640006E317 /* NRHomeNovelHeaderContentView.swift */; }; + 039810AE2ED3F3400006E317 /* NRHomeNovelMustReadTodayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810AD2ED3F3400006E317 /* NRHomeNovelMustReadTodayView.swift */; }; + 039810B02ED3F50C0006E317 /* NRHomeNovelMustReadTodayCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810AF2ED3F50C0006E317 /* NRHomeNovelMustReadTodayCell.swift */; }; + 039810B22ED3F5FB0006E317 /* NRGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810B12ED3F5FB0006E317 /* NRGradientView.swift */; }; + 039810B42ED428F20006E317 /* NRHomeNovelNewArrivalsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810B32ED428F20006E317 /* NRHomeNovelNewArrivalsView.swift */; }; + 039810B62ED42D840006E317 /* NRHomeNovelNewArrivalsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810B52ED42D840006E317 /* NRHomeNovelNewArrivalsCell.swift */; }; + 039810B82ED431780006E317 /* NRHomeNovelReadWhatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810B72ED431780006E317 /* NRHomeNovelReadWhatView.swift */; }; + 039810BA2ED4377E0006E317 /* NRHomeNovelReadWhatCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810B92ED4377E0006E317 /* NRHomeNovelReadWhatCell.swift */; }; + 039810BC2ED43C8E0006E317 /* NRReadWhatViewTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810BB2ED43C8E0006E317 /* NRReadWhatViewTransformer.swift */; }; + 039810BE2ED44C210006E317 /* NRHomeNovelHotGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810BD2ED44C210006E317 /* NRHomeNovelHotGridView.swift */; }; + 039810C02ED44D990006E317 /* NRHomeNovelNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810BF2ED44D990006E317 /* NRHomeNovelNextView.swift */; }; + 039810C22ED456560006E317 /* NRHomeNovelNextViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810C12ED456560006E317 /* NRHomeNovelNextViewCell.swift */; }; + 039810C42ED459440006E317 /* NRHomeNovelHotTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810C32ED459440006E317 /* NRHomeNovelHotTagView.swift */; }; + 039810C62ED45AE30006E317 /* NRHomeNovelHotTagCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810C52ED45AE30006E317 /* NRHomeNovelHotTagCell.swift */; }; + 039810CA2ED469D50006E317 /* NRWaterfallFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810C92ED469D50006E317 /* NRWaterfallFlowLayout.swift */; }; + 039810CC2ED477CD0006E317 /* UIView+NRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810CB2ED477C60006E317 /* UIView+NRAdd.swift */; }; + 039810CE2ED47A130006E317 /* CGMutablePath+NRRoundedCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810CD2ED47A0C0006E317 /* CGMutablePath+NRRoundedCorner.swift */; }; + 039810D02ED54D370006E317 /* NRHomeCategoryTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810CF2ED54D370006E317 /* NRHomeCategoryTagView.swift */; }; + 039810D22ED54F190006E317 /* NRHomeNovelListTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039810D12ED54F190006E317 /* NRHomeNovelListTextCell.swift */; }; + 67DC33BD353DB9F2D4C0FFE8 /* Pods_ReaderHive.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43E8F76284854E8FD006C875 /* Pods_ReaderHive.framework */; }; + F34348AF2ED5B85300AA7E70 /* NRExploreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348AE2ED5B85300AA7E70 /* NRExploreViewController.swift */; }; + F34348B12ED5B9A400AA7E70 /* NRExploreNovelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348B02ED5B9A400AA7E70 /* NRExploreNovelViewController.swift */; }; + F34348B32ED5BB6100AA7E70 /* NRExploreNovelMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348B22ED5BB6100AA7E70 /* NRExploreNovelMenuView.swift */; }; + F34348B52ED5C6F800AA7E70 /* NRTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348B42ED5C6F800AA7E70 /* NRTableView.swift */; }; + F34348B72ED5C75800AA7E70 /* NRTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348B62ED5C75800AA7E70 /* NRTableViewCell.swift */; }; + F34348B92ED5C7E400AA7E70 /* NRExploreNovelMenuCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348B82ED5C7E400AA7E70 /* NRExploreNovelMenuCell.swift */; }; + F34348BB2ED5CD8100AA7E70 /* NRExploreNovelMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348BA2ED5CD8100AA7E70 /* NRExploreNovelMenuItem.swift */; }; + F34348BD2ED68F2B00AA7E70 /* NRExploreNovelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348BC2ED68F2B00AA7E70 /* NRExploreNovelViewModel.swift */; }; + F34348BF2ED691C100AA7E70 /* NRExploreNovelGenresViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348BE2ED691C100AA7E70 /* NRExploreNovelGenresViewController.swift */; }; + F34348C12ED693E900AA7E70 /* NRExploreNovelGenresCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348C02ED693E900AA7E70 /* NRExploreNovelGenresCell.swift */; }; + F34348C32ED6A20700AA7E70 /* NRExploreNovelContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348C22ED6A20700AA7E70 /* NRExploreNovelContentViewController.swift */; }; + F34348C52ED6CA4D00AA7E70 /* NRExploreNovelMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348C42ED6CA4D00AA7E70 /* NRExploreNovelMenuDataSource.swift */; }; + F34348C72ED6CCBC00AA7E70 /* NRExploreNovelContentListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348C62ED6CCBC00AA7E70 /* NRExploreNovelContentListViewController.swift */; }; + F34348C92ED6CDD900AA7E70 /* NRExploreNovelContentListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348C82ED6CDD900AA7E70 /* NRExploreNovelContentListCell.swift */; }; + F34348CB2ED6DADA00AA7E70 /* NRNovelGenresViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348CA2ED6DADA00AA7E70 /* NRNovelGenresViewController.swift */; }; + F34348CD2ED6DD0900AA7E70 /* NRNovelGenresCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348CC2ED6DD0900AA7E70 /* NRNovelGenresCell.swift */; }; + F34348D32ED6E0F400AA7E70 /* NRMyListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348D22ED6E0F400AA7E70 /* NRMyListViewController.swift */; }; + F34348D52ED6E16500AA7E70 /* NRMyListNovelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348D42ED6E16500AA7E70 /* NRMyListNovelViewController.swift */; }; + F34348D72ED6E7C600AA7E70 /* NRMyListNovelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348D62ED6E7C600AA7E70 /* NRMyListNovelCell.swift */; }; + F34348D92ED6F01900AA7E70 /* NRNovelDetailBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348D82ED6F01900AA7E70 /* NRNovelDetailBottomView.swift */; }; + F34348DB2ED6F80A00AA7E70 /* NRNovelReaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348DA2ED6F80A00AA7E70 /* NRNovelReaderViewController.swift */; }; + F34348DD2ED6F9F900AA7E70 /* NRNovelDetailMoreLikeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348DC2ED6F9F900AA7E70 /* NRNovelDetailMoreLikeCell.swift */; }; + F34348DF2ED7049E00AA7E70 /* NRNovelDetailHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348DE2ED7049E00AA7E70 /* NRNovelDetailHeaderView.swift */; }; + F34348E12ED70A2700AA7E70 /* NRNovelDetailHeaderView+NovelCoverInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348E02ED70A2200AA7E70 /* NRNovelDetailHeaderView+NovelCoverInfo.swift */; }; + F34348E32ED70D2F00AA7E70 /* NRNovelDetailHeaderView+Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348E22ED70D2300AA7E70 /* NRNovelDetailHeaderView+Data.swift */; }; + F34348E72ED7F91C00AA7E70 /* NSNumber+NRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348E62ED7F91500AA7E70 /* NSNumber+NRAdd.swift */; }; + F34348E92ED7FA9100AA7E70 /* NRNovelDetailRecommandViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348E82ED7FA9100AA7E70 /* NRNovelDetailRecommandViewController.swift */; }; + F34348EB2ED82B4100AA7E70 /* NRNovelReadSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348EA2ED82B4100AA7E70 /* NRNovelReadSet.swift */; }; + F34348ED2ED82B6300AA7E70 /* NRNovelReadSetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348EC2ED82B6300AA7E70 /* NRNovelReadSetManager.swift */; }; + F34348F02ED8381E00AA7E70 /* NRNovelReadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348EF2ED8381E00AA7E70 /* NRNovelReadViewModel.swift */; }; + F34348F22ED8388F00AA7E70 /* NRNovelReadTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348F12ED8388F00AA7E70 /* NRNovelReadTopView.swift */; }; + F34348F52ED848EC00AA7E70 /* NRNovelReadPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348F42ED848EC00AA7E70 /* NRNovelReadPageViewController.swift */; }; + F34348F72ED84B0D00AA7E70 /* NRNovelReaderViewController+Page.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348F62ED84B0900AA7E70 /* NRNovelReaderViewController+Page.swift */; }; + F34348F92ED855AA00AA7E70 /* NRNovelReadContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348F82ED855AA00AA7E70 /* NRNovelReadContentViewController.swift */; }; + F34348FB2ED8560300AA7E70 /* NRNovelReadContentTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348FA2ED8560300AA7E70 /* NRNovelReadContentTopView.swift */; }; + F34348FD2ED8561300AA7E70 /* NRNovelReadContentBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348FC2ED8561300AA7E70 /* NRNovelReadContentBottomView.swift */; }; + F34348FF2ED85BF200AA7E70 /* NRReadBatteryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34348FE2ED85BF200AA7E70 /* NRReadBatteryView.swift */; }; + F34349012ED93A9B00AA7E70 /* NRReadChapterModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349002ED93A9B00AA7E70 /* NRReadChapterModel.swift */; }; + F34349032ED943C300AA7E70 /* NRReadParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349022ED943C300AA7E70 /* NRReadParser.swift */; }; + F34349052ED9442300AA7E70 /* NRReadPageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349042ED9442300AA7E70 /* NRReadPageModel.swift */; }; + F34349082ED945DA00AA7E70 /* NRCoreText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349072ED945DA00AA7E70 /* NRCoreText.swift */; }; + F343490A2ED96EE600AA7E70 /* NRNovelReadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349092ED96EE600AA7E70 /* NRNovelReadView.swift */; }; + F343490C2ED9751800AA7E70 /* NRReadChapterCatalogModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343490B2ED9751800AA7E70 /* NRReadChapterCatalogModel.swift */; }; + F343490E2ED9A5D000AA7E70 /* NRNovelReadMoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343490D2ED9A5D000AA7E70 /* NRNovelReadMoreView.swift */; }; + F34349102ED9A77A00AA7E70 /* NRPanModalContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343490F2ED9A77A00AA7E70 /* NRPanModalContentView.swift */; }; + F34349122EDA84F100AA7E70 /* NRProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349112EDA84F100AA7E70 /* NRProgressView.swift */; }; + F34349142EDA9AE900AA7E70 /* NRNovelReadSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349132EDA9AE900AA7E70 /* NRNovelReadSettingView.swift */; }; + F34349162EDA9FC700AA7E70 /* NRReadSettingBrightnessView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349152EDA9FC700AA7E70 /* NRReadSettingBrightnessView.swift */; }; + F34349182EDAA02900AA7E70 /* NRNovelReadSettingItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349172EDAA02900AA7E70 /* NRNovelReadSettingItemView.swift */; }; + F343491A2EDAC2E500AA7E70 /* NRReadSettingThemeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349192EDAC2E500AA7E70 /* NRReadSettingThemeView.swift */; }; + F343491E2EDAD0AA00AA7E70 /* NRReadTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343491D2EDAD0AA00AA7E70 /* NRReadTheme.swift */; }; + F34349222EDD227A00AA7E70 /* NRReadSettingSpacingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349212EDD227A00AA7E70 /* NRReadSettingSpacingView.swift */; }; + F34349242EDD3C2400AA7E70 /* NRNovelReadBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349232EDD3C2400AA7E70 /* NRNovelReadBottomView.swift */; }; + F34349262EDD6E6600AA7E70 /* NRNovelReaderCatalogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349252EDD6E6600AA7E70 /* NRNovelReaderCatalogView.swift */; }; + F34349282EDD815D00AA7E70 /* NRNovelCatalogCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349272EDD815D00AA7E70 /* NRNovelCatalogCell.swift */; }; + F343492A2EDD93AD00AA7E70 /* NRReadSettingFontView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34349292EDD93AD00AA7E70 /* NRReadSettingFontView.swift */; }; + F343492C2EDE72F300AA7E70 /* NRHomeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343492B2EDE72EE00AA7E70 /* NRHomeAPI.swift */; }; + F34990B72EDE787A0039E939 /* UIScrollView+Refresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990B62EDE78680039E939 /* UIScrollView+Refresh.swift */; }; + F34990B92EDEB1620039E939 /* NRHomeNovelModuleItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990B82EDEB1620039E939 /* NRHomeNovelModuleItem.swift */; }; + F34990BB2EDEB2080039E939 /* NRHomeNovelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990BA2EDEB2080039E939 /* NRHomeNovelViewModel.swift */; }; + F34990BD2EDEC24E0039E939 /* NRStarGradeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990BC2EDEC24E0039E939 /* NRStarGradeView.swift */; }; + F34990BF2EDEDDCF0039E939 /* NRCategoryModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990BE2EDEDDCF0039E939 /* NRCategoryModel.swift */; }; + F34990C52EDFCD4A0039E939 /* NRMeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990C42EDFCD4A0039E939 /* NRMeViewController.swift */; }; + F34990C72EDFCE500039E939 /* NRNovelReadGradeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990C62EDFCE500039E939 /* NRNovelReadGradeView.swift */; }; + F34990F12EE00F5A0039E939 /* NRKeyedArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990F02EE00F550039E939 /* NRKeyedArchiver.swift */; }; + F34990F32EE02FD60039E939 /* NRNovelReadFinishViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990F22EE02FD60039E939 /* NRNovelReadFinishViewController.swift */; }; + F34990F52EE0346B0039E939 /* NRNovelReadBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990F42EE0346B0039E939 /* NRNovelReadBaseViewController.swift */; }; + F34990F92EE118FC0039E939 /* NRNovelReadFinishHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990F82EE118FC0039E939 /* NRNovelReadFinishHeaderView.swift */; }; + F34990FB2EE121490039E939 /* NRLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990FA2EE121490039E939 /* NRLabel.swift */; }; + F34990FD2EE124CF0039E939 /* NRNovelReadStarGradeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990FC2EE124CF0039E939 /* NRNovelReadStarGradeView.swift */; }; + F34990FF2EE158790039E939 /* NRMeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34990FE2EE158790039E939 /* NRMeCell.swift */; }; + F34991012EE1593A0039E939 /* NRMeHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991002EE1593A0039E939 /* NRMeHeaderView.swift */; }; + F34991032EE160F00039E939 /* NRUserAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991022EE160E50039E939 /* NRUserAPI.swift */; }; + F34991052EE165EA0039E939 /* NRMeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991042EE165EA0039E939 /* NRMeItem.swift */; }; + F34991072EE167E80039E939 /* NRAboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991062EE167E80039E939 /* NRAboutViewController.swift */; }; + F34991092EE169C60039E939 /* NRAboutHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991082EE169C60039E939 /* NRAboutHeaderView.swift */; }; + F349910B2EE16B520039E939 /* NRAboutCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F349910A2EE16B520039E939 /* NRAboutCell.swift */; }; + F349910E2EE1707C0039E939 /* NRWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F349910D2EE1707C0039E939 /* NRWebViewController.swift */; }; + F34991102EE1708C0039E939 /* NRWebViewController+Script.swift in Sources */ = {isa = PBXBuildFile; fileRef = F349910F2EE170850039E939 /* NRWebViewController+Script.swift */; }; + F34991122EE170E20039E939 /* NRWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991112EE170DD0039E939 /* NRWebView.swift */; }; + F34991142EE175E30039E939 /* NRHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991132EE175E30039E939 /* NRHistoryViewController.swift */; }; + F34991162EE176640039E939 /* NRNovelHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991152EE176640039E939 /* NRNovelHistoryViewController.swift */; }; + F34991182EE1780A0039E939 /* NRNovelHistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991172EE1780A0039E939 /* NRNovelHistoryCell.swift */; }; + F349911A2EE188E50039E939 /* NRHomeNovelNewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991192EE188E40039E939 /* NRHomeNovelNewViewController.swift */; }; + F349911C2EE190590039E939 /* NRNovelDetailCatalogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F349911B2EE190590039E939 /* NRNovelDetailCatalogViewController.swift */; }; + F349911F2EE26C350039E939 /* NRAlertWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F349911E2EE26C350039E939 /* NRAlertWindowManager.swift */; }; + F34991212EE26C660039E939 /* NRBaseAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991202EE26C660039E939 /* NRBaseAlert.swift */; }; + F34991232EE26EAC0039E939 /* NRAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991222EE26EAC0039E939 /* NRAlert.swift */; }; + F34991252EE27DB60039E939 /* NRGradientButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991242EE27DB60039E939 /* NRGradientButton.swift */; }; + F34991272EE2826D0039E939 /* NRNovelReadViewModel+Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991262EE282660039E939 /* NRNovelReadViewModel+Data.swift */; }; + F34991292EE285660039E939 /* NRShowRecommendPop.swift in Sources */ = {isa = PBXBuildFile; fileRef = F34991282EE285660039E939 /* NRShowRecommendPop.swift */; }; + F3B859312EE66B950095A9CC /* NRDetailRechargeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859302EE66B950095A9CC /* NRDetailRechargeView.swift */; }; + F3B859332EE66DD10095A9CC /* NRMeCoinsContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859322EE66DD10095A9CC /* NRMeCoinsContentView.swift */; }; + F3B859352EE66F530095A9CC /* NRMeCoinsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859342EE66F530095A9CC /* NRMeCoinsView.swift */; }; + F3B859372EE6750B0095A9CC /* NRLanguageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859362EE6750B0095A9CC /* NRLanguageViewController.swift */; }; + F3B859392EE676610095A9CC /* NRLanguageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B859382EE676610095A9CC /* NRLanguageCell.swift */; }; + F3B8593C2EE677170095A9CC /* NRLanguageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B8593B2EE677170095A9CC /* NRLanguageModel.swift */; }; + 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 */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0373D93F2ED57B1C0017DCC7 /* NRNovelDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelDetailViewController.swift; sourceTree = ""; }; + 0373D9442ED57B7B0017DCC7 /* NRNovelDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelDetailViewModel.swift; sourceTree = ""; }; + 0373D9462ED57F3A0017DCC7 /* UINavigationBar+NRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+NRAdd.swift"; sourceTree = ""; }; + 0373D94A2ED582E10017DCC7 /* NRNovelAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelAPI.swift; sourceTree = ""; }; + 0373D94C2ED583A80017DCC7 /* NRNovelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelModel.swift; sourceTree = ""; }; + 0373D94E2ED58A1E0017DCC7 /* NRSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRSearchViewController.swift; sourceTree = ""; }; + 0373D9512ED58A950017DCC7 /* NRSearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRSearchViewModel.swift; sourceTree = ""; }; + 0373D9532ED58AF00017DCC7 /* NRSearchInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRSearchInputView.swift; sourceTree = ""; }; + 0373D9552ED5933F0017DCC7 /* NRSearchHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRSearchHomeView.swift; sourceTree = ""; }; + 0373D9572ED5935D0017DCC7 /* NRSearchRecordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRSearchRecordView.swift; sourceTree = ""; }; + 0373D9592ED593D50017DCC7 /* NRSearchRecordCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRSearchRecordCell.swift; sourceTree = ""; }; + 0373D95B2ED598830017DCC7 /* UIStackView+NRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+NRAdd.swift"; sourceTree = ""; }; + 0373D95D2ED59C430017DCC7 /* NRSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRSearchResultView.swift; sourceTree = ""; }; + 0373D95F2ED59DA10017DCC7 /* NRSearchResultCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRSearchResultCell.swift; sourceTree = ""; }; + 0373D9632ED5ABB80017DCC7 /* NREmpty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NREmpty.swift; sourceTree = ""; }; + 03980F652ED009E30006E317 /* ReaderHive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReaderHive.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 03980F7D2ED009EB0006E317 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 03980F7E2ED009EB0006E317 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 03980F7F2ED009EB0006E317 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 03980F802ED009EB0006E317 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 03980F842ED009EB0006E317 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 0398105B2ED047FE0006E317 /* NRTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRTabBarController.swift; sourceTree = ""; }; + 0398105D2ED0481E0006E317 /* NRNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNavigationController.swift; sourceTree = ""; }; + 0398105F2ED04D950006E317 /* NRNetworkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNetworkModel.swift; sourceTree = ""; }; + 039810612ED04F250006E317 /* NRNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNetwork.swift; sourceTree = ""; }; + 039810632ED04F480006E317 /* NRTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRTargetType.swift; sourceTree = ""; }; + 039810652ED04F940006E317 /* NRUrlPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRUrlPath.swift; sourceTree = ""; }; + 039810672ED050390006E317 /* NRResponseCryptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRResponseCryptor.swift; sourceTree = ""; }; + 039810692ED0505D0006E317 /* NRNetworkReachableManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNetworkReachableManager.swift; sourceTree = ""; }; + 0398106C2ED052FC0006E317 /* NRDefine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRDefine.swift; sourceTree = ""; }; + 0398106F2ED053850006E317 /* String+NRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+NRAdd.swift"; sourceTree = ""; }; + 039810722ED053BE0006E317 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 039810772ED054740006E317 /* NRHud.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHud.swift; sourceTree = ""; }; + 039810792ED054E40006E317 /* NRToast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRToast.swift; sourceTree = ""; }; + 0398107B2ED0551C0006E317 /* AppDelegate+Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Config.swift"; sourceTree = ""; }; + 0398107E2ED055D10006E317 /* NRLoginManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRLoginManager.swift; sourceTree = ""; }; + 039810802ED056090006E317 /* NRLoginToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRLoginToken.swift; sourceTree = ""; }; + 039810822ED0563D0006E317 /* NRUserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRUserInfo.swift; sourceTree = ""; }; + 039810842ED056D00006E317 /* UserDefaults+NRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+NRAdd.swift"; sourceTree = ""; }; + 039810862ED057210006E317 /* NRUserDefaultsKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRUserDefaultsKey.swift; sourceTree = ""; }; + 039810892ED0582B0006E317 /* NRDeviceId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRDeviceId.swift; sourceTree = ""; }; + 0398108B2ED0584F0006E317 /* NRKeychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRKeychain.swift; sourceTree = ""; }; + 0398108D2ED05FFA0006E317 /* UIFont+NRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+NRAdd.swift"; sourceTree = ""; }; + 0398108F2ED060EF0006E317 /* NRViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRViewController.swift; sourceTree = ""; }; + 039810922ED062CE0006E317 /* NRHomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeViewController.swift; sourceTree = ""; }; + 039810942ED066710006E317 /* UIScreen+NRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScreen+NRAdd.swift"; sourceTree = ""; }; + 039810972ED066B20006E317 /* NRTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRTool.swift; sourceTree = ""; }; + 0398109A2ED0692A0006E317 /* NRImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRImageView.swift; sourceTree = ""; }; + 0398109F2ED06B7C0006E317 /* NRHomeNovelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelViewController.swift; sourceTree = ""; }; + 039810A12ED070400006E317 /* NRHomeNovelHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelHeaderView.swift; sourceTree = ""; }; + 039810A32ED072380006E317 /* NRHomeNovelListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelListViewController.swift; sourceTree = ""; }; + 039810A52ED072820006E317 /* NRCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRCollectionView.swift; sourceTree = ""; }; + 039810A72ED3E7DB0006E317 /* NRHomeNovelListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelListCell.swift; sourceTree = ""; }; + 039810A92ED3EC2E0006E317 /* NRScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRScrollView.swift; sourceTree = ""; }; + 039810AB2ED3EF640006E317 /* NRHomeNovelHeaderContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelHeaderContentView.swift; sourceTree = ""; }; + 039810AD2ED3F3400006E317 /* NRHomeNovelMustReadTodayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelMustReadTodayView.swift; sourceTree = ""; }; + 039810AF2ED3F50C0006E317 /* NRHomeNovelMustReadTodayCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelMustReadTodayCell.swift; sourceTree = ""; }; + 039810B12ED3F5FB0006E317 /* NRGradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRGradientView.swift; sourceTree = ""; }; + 039810B32ED428F20006E317 /* NRHomeNovelNewArrivalsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelNewArrivalsView.swift; sourceTree = ""; }; + 039810B52ED42D840006E317 /* NRHomeNovelNewArrivalsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelNewArrivalsCell.swift; sourceTree = ""; }; + 039810B72ED431780006E317 /* NRHomeNovelReadWhatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelReadWhatView.swift; sourceTree = ""; }; + 039810B92ED4377E0006E317 /* NRHomeNovelReadWhatCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelReadWhatCell.swift; sourceTree = ""; }; + 039810BB2ED43C8E0006E317 /* NRReadWhatViewTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRReadWhatViewTransformer.swift; sourceTree = ""; }; + 039810BD2ED44C210006E317 /* NRHomeNovelHotGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelHotGridView.swift; sourceTree = ""; }; + 039810BF2ED44D990006E317 /* NRHomeNovelNextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelNextView.swift; sourceTree = ""; }; + 039810C12ED456560006E317 /* NRHomeNovelNextViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelNextViewCell.swift; sourceTree = ""; }; + 039810C32ED459440006E317 /* NRHomeNovelHotTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelHotTagView.swift; sourceTree = ""; }; + 039810C52ED45AE30006E317 /* NRHomeNovelHotTagCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelHotTagCell.swift; sourceTree = ""; }; + 039810C92ED469D50006E317 /* NRWaterfallFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRWaterfallFlowLayout.swift; sourceTree = ""; }; + 039810CB2ED477C60006E317 /* UIView+NRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+NRAdd.swift"; sourceTree = ""; }; + 039810CD2ED47A0C0006E317 /* CGMutablePath+NRRoundedCorner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGMutablePath+NRRoundedCorner.swift"; sourceTree = ""; }; + 039810CF2ED54D370006E317 /* NRHomeCategoryTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeCategoryTagView.swift; sourceTree = ""; }; + 039810D12ED54F190006E317 /* NRHomeNovelListTextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelListTextCell.swift; sourceTree = ""; }; + 3258B4947CC1FF8C380F3090 /* Pods-ReaderHive.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReaderHive.debug.xcconfig"; path = "Target Support Files/Pods-ReaderHive/Pods-ReaderHive.debug.xcconfig"; sourceTree = ""; }; + 33A96151B23BA2ED4F2CCF56 /* Pods-ReaderHive.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReaderHive.release.xcconfig"; path = "Target Support Files/Pods-ReaderHive/Pods-ReaderHive.release.xcconfig"; sourceTree = ""; }; + 43E8F76284854E8FD006C875 /* Pods_ReaderHive.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReaderHive.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 455BEF77457E224E14EB6CCA /* Pods-NovelReader.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NovelReader.release.xcconfig"; path = "Target Support Files/Pods-NovelReader/Pods-NovelReader.release.xcconfig"; sourceTree = ""; }; + C3BEE224CB3F55939653D26D /* Pods-NovelReader.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NovelReader.debug.xcconfig"; path = "Target Support Files/Pods-NovelReader/Pods-NovelReader.debug.xcconfig"; sourceTree = ""; }; + F34348AE2ED5B85300AA7E70 /* NRExploreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreViewController.swift; sourceTree = ""; }; + F34348B02ED5B9A400AA7E70 /* NRExploreNovelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreNovelViewController.swift; sourceTree = ""; }; + F34348B22ED5BB6100AA7E70 /* NRExploreNovelMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreNovelMenuView.swift; sourceTree = ""; }; + F34348B42ED5C6F800AA7E70 /* NRTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRTableView.swift; sourceTree = ""; }; + F34348B62ED5C75800AA7E70 /* NRTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRTableViewCell.swift; sourceTree = ""; }; + F34348B82ED5C7E400AA7E70 /* NRExploreNovelMenuCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreNovelMenuCell.swift; sourceTree = ""; }; + F34348BA2ED5CD8100AA7E70 /* NRExploreNovelMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreNovelMenuItem.swift; sourceTree = ""; }; + F34348BC2ED68F2B00AA7E70 /* NRExploreNovelViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreNovelViewModel.swift; sourceTree = ""; }; + F34348BE2ED691C100AA7E70 /* NRExploreNovelGenresViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreNovelGenresViewController.swift; sourceTree = ""; }; + F34348C02ED693E900AA7E70 /* NRExploreNovelGenresCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreNovelGenresCell.swift; sourceTree = ""; }; + F34348C22ED6A20700AA7E70 /* NRExploreNovelContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreNovelContentViewController.swift; sourceTree = ""; }; + F34348C42ED6CA4D00AA7E70 /* NRExploreNovelMenuDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreNovelMenuDataSource.swift; sourceTree = ""; }; + F34348C62ED6CCBC00AA7E70 /* NRExploreNovelContentListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreNovelContentListViewController.swift; sourceTree = ""; }; + F34348C82ED6CDD900AA7E70 /* NRExploreNovelContentListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRExploreNovelContentListCell.swift; sourceTree = ""; }; + F34348CA2ED6DADA00AA7E70 /* NRNovelGenresViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelGenresViewController.swift; sourceTree = ""; }; + F34348CC2ED6DD0900AA7E70 /* NRNovelGenresCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelGenresCell.swift; sourceTree = ""; }; + F34348D22ED6E0F400AA7E70 /* NRMyListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRMyListViewController.swift; sourceTree = ""; }; + F34348D42ED6E16500AA7E70 /* NRMyListNovelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRMyListNovelViewController.swift; sourceTree = ""; }; + F34348D62ED6E7C600AA7E70 /* NRMyListNovelCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRMyListNovelCell.swift; sourceTree = ""; }; + F34348D82ED6F01900AA7E70 /* NRNovelDetailBottomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelDetailBottomView.swift; sourceTree = ""; }; + F34348DA2ED6F80A00AA7E70 /* NRNovelReaderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReaderViewController.swift; sourceTree = ""; }; + F34348DC2ED6F9F900AA7E70 /* NRNovelDetailMoreLikeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelDetailMoreLikeCell.swift; sourceTree = ""; }; + F34348DE2ED7049E00AA7E70 /* NRNovelDetailHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelDetailHeaderView.swift; sourceTree = ""; }; + F34348E02ED70A2200AA7E70 /* NRNovelDetailHeaderView+NovelCoverInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NRNovelDetailHeaderView+NovelCoverInfo.swift"; sourceTree = ""; }; + F34348E22ED70D2300AA7E70 /* NRNovelDetailHeaderView+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NRNovelDetailHeaderView+Data.swift"; sourceTree = ""; }; + F34348E62ED7F91500AA7E70 /* NSNumber+NRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNumber+NRAdd.swift"; sourceTree = ""; }; + F34348E82ED7FA9100AA7E70 /* NRNovelDetailRecommandViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelDetailRecommandViewController.swift; sourceTree = ""; }; + F34348EA2ED82B4100AA7E70 /* NRNovelReadSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadSet.swift; sourceTree = ""; }; + F34348EC2ED82B6300AA7E70 /* NRNovelReadSetManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadSetManager.swift; sourceTree = ""; }; + F34348EF2ED8381E00AA7E70 /* NRNovelReadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadViewModel.swift; sourceTree = ""; }; + F34348F12ED8388F00AA7E70 /* NRNovelReadTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadTopView.swift; sourceTree = ""; }; + F34348F42ED848EC00AA7E70 /* NRNovelReadPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadPageViewController.swift; sourceTree = ""; }; + F34348F62ED84B0900AA7E70 /* NRNovelReaderViewController+Page.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NRNovelReaderViewController+Page.swift"; sourceTree = ""; }; + F34348F82ED855AA00AA7E70 /* NRNovelReadContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadContentViewController.swift; sourceTree = ""; }; + F34348FA2ED8560300AA7E70 /* NRNovelReadContentTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadContentTopView.swift; sourceTree = ""; }; + F34348FC2ED8561300AA7E70 /* NRNovelReadContentBottomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadContentBottomView.swift; sourceTree = ""; }; + F34348FE2ED85BF200AA7E70 /* NRReadBatteryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRReadBatteryView.swift; sourceTree = ""; }; + F34349002ED93A9B00AA7E70 /* NRReadChapterModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRReadChapterModel.swift; sourceTree = ""; }; + F34349022ED943C300AA7E70 /* NRReadParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRReadParser.swift; sourceTree = ""; }; + F34349042ED9442300AA7E70 /* NRReadPageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRReadPageModel.swift; sourceTree = ""; }; + F34349072ED945DA00AA7E70 /* NRCoreText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRCoreText.swift; sourceTree = ""; }; + F34349092ED96EE600AA7E70 /* NRNovelReadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadView.swift; sourceTree = ""; }; + F343490B2ED9751800AA7E70 /* NRReadChapterCatalogModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRReadChapterCatalogModel.swift; sourceTree = ""; }; + F343490D2ED9A5D000AA7E70 /* NRNovelReadMoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadMoreView.swift; sourceTree = ""; }; + F343490F2ED9A77A00AA7E70 /* NRPanModalContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRPanModalContentView.swift; sourceTree = ""; }; + F34349112EDA84F100AA7E70 /* NRProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRProgressView.swift; sourceTree = ""; }; + F34349132EDA9AE900AA7E70 /* NRNovelReadSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadSettingView.swift; sourceTree = ""; }; + F34349152EDA9FC700AA7E70 /* NRReadSettingBrightnessView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRReadSettingBrightnessView.swift; sourceTree = ""; }; + F34349172EDAA02900AA7E70 /* NRNovelReadSettingItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadSettingItemView.swift; sourceTree = ""; }; + F34349192EDAC2E500AA7E70 /* NRReadSettingThemeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRReadSettingThemeView.swift; sourceTree = ""; }; + F343491D2EDAD0AA00AA7E70 /* NRReadTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRReadTheme.swift; sourceTree = ""; }; + F34349212EDD227A00AA7E70 /* NRReadSettingSpacingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRReadSettingSpacingView.swift; sourceTree = ""; }; + F34349232EDD3C2400AA7E70 /* NRNovelReadBottomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadBottomView.swift; sourceTree = ""; }; + F34349252EDD6E6600AA7E70 /* NRNovelReaderCatalogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReaderCatalogView.swift; sourceTree = ""; }; + F34349272EDD815D00AA7E70 /* NRNovelCatalogCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelCatalogCell.swift; sourceTree = ""; }; + F34349292EDD93AD00AA7E70 /* NRReadSettingFontView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRReadSettingFontView.swift; sourceTree = ""; }; + F343492B2EDE72EE00AA7E70 /* NRHomeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeAPI.swift; sourceTree = ""; }; + F34990B62EDE78680039E939 /* UIScrollView+Refresh.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Refresh.swift"; sourceTree = ""; }; + F34990B82EDEB1620039E939 /* NRHomeNovelModuleItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelModuleItem.swift; sourceTree = ""; }; + F34990BA2EDEB2080039E939 /* NRHomeNovelViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelViewModel.swift; sourceTree = ""; }; + F34990BC2EDEC24E0039E939 /* NRStarGradeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRStarGradeView.swift; sourceTree = ""; }; + F34990BE2EDEDDCF0039E939 /* NRCategoryModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRCategoryModel.swift; sourceTree = ""; }; + F34990C42EDFCD4A0039E939 /* NRMeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRMeViewController.swift; sourceTree = ""; }; + F34990C62EDFCE500039E939 /* NRNovelReadGradeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadGradeView.swift; sourceTree = ""; }; + F34990F02EE00F550039E939 /* NRKeyedArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRKeyedArchiver.swift; sourceTree = ""; }; + F34990F22EE02FD60039E939 /* NRNovelReadFinishViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadFinishViewController.swift; sourceTree = ""; }; + F34990F42EE0346B0039E939 /* NRNovelReadBaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadBaseViewController.swift; sourceTree = ""; }; + F34990F82EE118FC0039E939 /* NRNovelReadFinishHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadFinishHeaderView.swift; sourceTree = ""; }; + F34990FA2EE121490039E939 /* NRLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRLabel.swift; sourceTree = ""; }; + F34990FC2EE124CF0039E939 /* NRNovelReadStarGradeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadStarGradeView.swift; sourceTree = ""; }; + F34990FE2EE158790039E939 /* NRMeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRMeCell.swift; sourceTree = ""; }; + F34991002EE1593A0039E939 /* NRMeHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRMeHeaderView.swift; sourceTree = ""; }; + F34991022EE160E50039E939 /* NRUserAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRUserAPI.swift; sourceTree = ""; }; + F34991042EE165EA0039E939 /* NRMeItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRMeItem.swift; sourceTree = ""; }; + F34991062EE167E80039E939 /* NRAboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRAboutViewController.swift; sourceTree = ""; }; + F34991082EE169C60039E939 /* NRAboutHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRAboutHeaderView.swift; sourceTree = ""; }; + F349910A2EE16B520039E939 /* NRAboutCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRAboutCell.swift; sourceTree = ""; }; + F349910D2EE1707C0039E939 /* NRWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRWebViewController.swift; sourceTree = ""; }; + F349910F2EE170850039E939 /* NRWebViewController+Script.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NRWebViewController+Script.swift"; sourceTree = ""; }; + F34991112EE170DD0039E939 /* NRWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRWebView.swift; sourceTree = ""; }; + F34991132EE175E30039E939 /* NRHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHistoryViewController.swift; sourceTree = ""; }; + F34991152EE176640039E939 /* NRNovelHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelHistoryViewController.swift; sourceTree = ""; }; + F34991172EE1780A0039E939 /* NRNovelHistoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelHistoryCell.swift; sourceTree = ""; }; + F34991192EE188E40039E939 /* NRHomeNovelNewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeNovelNewViewController.swift; sourceTree = ""; }; + F349911B2EE190590039E939 /* NRNovelDetailCatalogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelDetailCatalogViewController.swift; sourceTree = ""; }; + F349911E2EE26C350039E939 /* NRAlertWindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRAlertWindowManager.swift; sourceTree = ""; }; + F34991202EE26C660039E939 /* NRBaseAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRBaseAlert.swift; sourceTree = ""; }; + F34991222EE26EAC0039E939 /* NRAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRAlert.swift; sourceTree = ""; }; + F34991242EE27DB60039E939 /* NRGradientButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRGradientButton.swift; sourceTree = ""; }; + F34991262EE282660039E939 /* NRNovelReadViewModel+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NRNovelReadViewModel+Data.swift"; sourceTree = ""; }; + F34991282EE285660039E939 /* NRShowRecommendPop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRShowRecommendPop.swift; sourceTree = ""; }; + F3B859302EE66B950095A9CC /* NRDetailRechargeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRDetailRechargeView.swift; sourceTree = ""; }; + F3B859322EE66DD10095A9CC /* NRMeCoinsContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRMeCoinsContentView.swift; sourceTree = ""; }; + F3B859342EE66F530095A9CC /* NRMeCoinsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRMeCoinsView.swift; sourceTree = ""; }; + F3B859362EE6750B0095A9CC /* NRLanguageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRLanguageViewController.swift; sourceTree = ""; }; + F3B859382EE676610095A9CC /* NRLanguageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRLanguageCell.swift; sourceTree = ""; }; + F3B8593B2EE677170095A9CC /* NRLanguageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRLanguageModel.swift; sourceTree = ""; }; + F3B8593D2EE677740095A9CC /* NRLocalizedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRLocalizedModel.swift; sourceTree = ""; }; + F3B8593F2EE6787E0095A9CC /* NRLocalizedManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRLocalizedManager.swift; sourceTree = ""; }; + F3B859412EE678FB0095A9CC /* NRSettingAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRSettingAPI.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 03980F622ED009E30006E317 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 67DC33BD353DB9F2D4C0FFE8 /* Pods_ReaderHive.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0373D93C2ED578FC0017DCC7 /* API */ = { + isa = PBXGroup; + children = ( + F34991022EE160E50039E939 /* NRUserAPI.swift */, + F343492B2EDE72EE00AA7E70 /* NRHomeAPI.swift */, + 0373D94A2ED582E10017DCC7 /* NRNovelAPI.swift */, + F3B859412EE678FB0095A9CC /* NRSettingAPI.swift */, + ); + path = API; + sourceTree = ""; + }; + 0373D93D2ED57A500017DCC7 /* Novel */ = { + isa = PBXGroup; + children = ( + 0373D93E2ED57B010017DCC7 /* VC */, + 0373D9422ED57B450017DCC7 /* V */, + 0373D9412ED57B3F0017DCC7 /* M */, + 0373D9432ED57B4A0017DCC7 /* VM */, + ); + path = Novel; + sourceTree = ""; + }; + 0373D93E2ED57B010017DCC7 /* VC */ = { + isa = PBXGroup; + children = ( + F34348F32ED848B300AA7E70 /* Read */, + 0373D93F2ED57B1C0017DCC7 /* NRNovelDetailViewController.swift */, + F34348E82ED7FA9100AA7E70 /* NRNovelDetailRecommandViewController.swift */, + F349911B2EE190590039E939 /* NRNovelDetailCatalogViewController.swift */, + ); + path = VC; + sourceTree = ""; + }; + 0373D9412ED57B3F0017DCC7 /* M */ = { + isa = PBXGroup; + children = ( + 0373D94C2ED583A80017DCC7 /* NRNovelModel.swift */, + F343490B2ED9751800AA7E70 /* NRReadChapterCatalogModel.swift */, + F34349002ED93A9B00AA7E70 /* NRReadChapterModel.swift */, + F34349042ED9442300AA7E70 /* NRReadPageModel.swift */, + F34990BE2EDEDDCF0039E939 /* NRCategoryModel.swift */, + F34991282EE285660039E939 /* NRShowRecommendPop.swift */, + ); + path = M; + sourceTree = ""; + }; + 0373D9422ED57B450017DCC7 /* V */ = { + isa = PBXGroup; + children = ( + F34348EE2ED82F7100AA7E70 /* Reader */, + F34348D82ED6F01900AA7E70 /* NRNovelDetailBottomView.swift */, + F34348DC2ED6F9F900AA7E70 /* NRNovelDetailMoreLikeCell.swift */, + F34348DE2ED7049E00AA7E70 /* NRNovelDetailHeaderView.swift */, + F34348E02ED70A2200AA7E70 /* NRNovelDetailHeaderView+NovelCoverInfo.swift */, + F34348E22ED70D2300AA7E70 /* NRNovelDetailHeaderView+Data.swift */, + F3B859302EE66B950095A9CC /* NRDetailRechargeView.swift */, + ); + path = V; + sourceTree = ""; + }; + 0373D9432ED57B4A0017DCC7 /* VM */ = { + isa = PBXGroup; + children = ( + 0373D9442ED57B7B0017DCC7 /* NRNovelDetailViewModel.swift */, + F34348EF2ED8381E00AA7E70 /* NRNovelReadViewModel.swift */, + F34991262EE282660039E939 /* NRNovelReadViewModel+Data.swift */, + ); + path = VM; + sourceTree = ""; + }; + 0373D9502ED58A780017DCC7 /* VM */ = { + isa = PBXGroup; + children = ( + 0373D9512ED58A950017DCC7 /* NRSearchViewModel.swift */, + F34990BA2EDEB2080039E939 /* NRHomeNovelViewModel.swift */, + ); + path = VM; + sourceTree = ""; + }; + 0373D9612ED5AB9F0017DCC7 /* Empty */ = { + isa = PBXGroup; + children = ( + 0373D9632ED5ABB80017DCC7 /* NREmpty.swift */, + ); + path = Empty; + sourceTree = ""; + }; + 03980F5C2ED009E30006E317 = { + isa = PBXGroup; + children = ( + 03980F862ED009EB0006E317 /* ReaderHive */, + 03980F662ED009E30006E317 /* Products */, + E0C2D753F0B7D561FB16D3E3 /* Pods */, + 7508E82FE7A8A9ED3762CFF3 /* Frameworks */, + ); + sourceTree = ""; + }; + 03980F662ED009E30006E317 /* Products */ = { + isa = PBXGroup; + children = ( + 03980F652ED009E30006E317 /* ReaderHive.app */, + ); + name = Products; + sourceTree = ""; + }; + 03980F862ED009EB0006E317 /* ReaderHive */ = { + isa = PBXGroup; + children = ( + 03980F8F2ED00ACD0006E317 /* Delegate */, + 039810562ED046030006E317 /* Base */, + 039810752ED054090006E317 /* Class */, + 03980F8E2ED00ABC0006E317 /* Source */, + 039810742ED053F40006E317 /* Libs */, + ); + path = ReaderHive; + sourceTree = ""; + }; + 03980F8E2ED00ABC0006E317 /* Source */ = { + isa = PBXGroup; + children = ( + 03980F7E2ED009EB0006E317 /* Assets.xcassets */, + 03980F7F2ED009EB0006E317 /* Info.plist */, + 03980F812ED009EB0006E317 /* LaunchScreen.storyboard */, + 039810712ED053BE0006E317 /* Localizable.strings */, + ); + path = Source; + sourceTree = ""; + }; + 03980F8F2ED00ACD0006E317 /* Delegate */ = { + isa = PBXGroup; + children = ( + 03980F7D2ED009EB0006E317 /* AppDelegate.swift */, + 03980F842ED009EB0006E317 /* SceneDelegate.swift */, + 0398107B2ED0551C0006E317 /* AppDelegate+Config.swift */, + ); + path = Delegate; + sourceTree = ""; + }; + 039810562ED046030006E317 /* Base */ = { + isa = PBXGroup; + children = ( + 0398105A2ED047DF0006E317 /* VC */, + 039810992ED069110006E317 /* View */, + F349910C2EE1702F0039E939 /* WebView */, + 0398106E2ED053630006E317 /* Extension */, + 0398106B2ED052E50006E317 /* Define */, + 039810572ED047BD0006E317 /* Networking */, + ); + path = Base; + sourceTree = ""; + }; + 039810572ED047BD0006E317 /* Networking */ = { + isa = PBXGroup; + children = ( + 0373D93C2ED578FC0017DCC7 /* API */, + 039810612ED04F250006E317 /* NRNetwork.swift */, + 039810632ED04F480006E317 /* NRTargetType.swift */, + 039810652ED04F940006E317 /* NRUrlPath.swift */, + 0398105F2ED04D950006E317 /* NRNetworkModel.swift */, + 039810672ED050390006E317 /* NRResponseCryptor.swift */, + 039810692ED0505D0006E317 /* NRNetworkReachableManager.swift */, + ); + path = Networking; + sourceTree = ""; + }; + 0398105A2ED047DF0006E317 /* VC */ = { + isa = PBXGroup; + children = ( + 0398105B2ED047FE0006E317 /* NRTabBarController.swift */, + 0398105D2ED0481E0006E317 /* NRNavigationController.swift */, + 0398108F2ED060EF0006E317 /* NRViewController.swift */, + ); + path = VC; + sourceTree = ""; + }; + 0398106B2ED052E50006E317 /* Define */ = { + isa = PBXGroup; + children = ( + 039810862ED057210006E317 /* NRUserDefaultsKey.swift */, + 0398106C2ED052FC0006E317 /* NRDefine.swift */, + ); + path = Define; + sourceTree = ""; + }; + 0398106E2ED053630006E317 /* Extension */ = { + isa = PBXGroup; + children = ( + F34990B62EDE78680039E939 /* UIScrollView+Refresh.swift */, + F34348E62ED7F91500AA7E70 /* NSNumber+NRAdd.swift */, + 0373D95B2ED598830017DCC7 /* UIStackView+NRAdd.swift */, + 0373D9462ED57F3A0017DCC7 /* UINavigationBar+NRAdd.swift */, + 039810CB2ED477C60006E317 /* UIView+NRAdd.swift */, + 0398108D2ED05FFA0006E317 /* UIFont+NRAdd.swift */, + 039810842ED056D00006E317 /* UserDefaults+NRAdd.swift */, + 0398106F2ED053850006E317 /* String+NRAdd.swift */, + 039810942ED066710006E317 /* UIScreen+NRAdd.swift */, + 039810CD2ED47A0C0006E317 /* CGMutablePath+NRRoundedCorner.swift */, + ); + path = Extension; + sourceTree = ""; + }; + 039810742ED053F40006E317 /* Libs */ = { + isa = PBXGroup; + children = ( + F3B8593A2EE676B30095A9CC /* LocalizedManager */, + F349911D2EE26B020039E939 /* Alert */, + F34990EF2EE00F460039E939 /* KeyedArchiver */, + F34349062ED9456100AA7E70 /* NovelTool */, + 0373D9612ED5AB9F0017DCC7 /* Empty */, + 039810C82ED469BC0006E317 /* WaterfallFlowLayout */, + 039810962ED0669A0006E317 /* Tool */, + 039810882ED058230006E317 /* DeviceId */, + 0398107D2ED055B50006E317 /* Login */, + 039810762ED054550006E317 /* Hud */, + ); + path = Libs; + sourceTree = ""; + }; + 039810752ED054090006E317 /* Class */ = { + isa = PBXGroup; + children = ( + 039810912ED062AD0006E317 /* Home */, + F34348CE2ED6E0BB00AA7E70 /* MyList */, + F34348A92ED5B59C00AA7E70 /* Explore */, + 0373D93D2ED57A500017DCC7 /* Novel */, + F34990C02EDFCD180039E939 /* Me */, + ); + path = Class; + sourceTree = ""; + }; + 039810762ED054550006E317 /* Hud */ = { + isa = PBXGroup; + children = ( + 039810772ED054740006E317 /* NRHud.swift */, + 039810792ED054E40006E317 /* NRToast.swift */, + ); + path = Hud; + sourceTree = ""; + }; + 0398107D2ED055B50006E317 /* Login */ = { + isa = PBXGroup; + children = ( + 0398107E2ED055D10006E317 /* NRLoginManager.swift */, + 039810802ED056090006E317 /* NRLoginToken.swift */, + 039810822ED0563D0006E317 /* NRUserInfo.swift */, + ); + path = Login; + sourceTree = ""; + }; + 039810882ED058230006E317 /* DeviceId */ = { + isa = PBXGroup; + children = ( + 039810892ED0582B0006E317 /* NRDeviceId.swift */, + 0398108B2ED0584F0006E317 /* NRKeychain.swift */, + ); + path = DeviceId; + sourceTree = ""; + }; + 039810912ED062AD0006E317 /* Home */ = { + isa = PBXGroup; + children = ( + 0398109C2ED0697A0006E317 /* C */, + 0398109D2ED069840006E317 /* V */, + 0398109E2ED069890006E317 /* M */, + 0373D9502ED58A780017DCC7 /* VM */, + ); + path = Home; + sourceTree = ""; + }; + 039810962ED0669A0006E317 /* Tool */ = { + isa = PBXGroup; + children = ( + 039810972ED066B20006E317 /* NRTool.swift */, + ); + path = Tool; + sourceTree = ""; + }; + 039810992ED069110006E317 /* View */ = { + isa = PBXGroup; + children = ( + 0398109A2ED0692A0006E317 /* NRImageView.swift */, + 039810A52ED072820006E317 /* NRCollectionView.swift */, + 039810A92ED3EC2E0006E317 /* NRScrollView.swift */, + 039810B12ED3F5FB0006E317 /* NRGradientView.swift */, + F34991242EE27DB60039E939 /* NRGradientButton.swift */, + F34348B42ED5C6F800AA7E70 /* NRTableView.swift */, + F34348B62ED5C75800AA7E70 /* NRTableViewCell.swift */, + F343490F2ED9A77A00AA7E70 /* NRPanModalContentView.swift */, + F34349112EDA84F100AA7E70 /* NRProgressView.swift */, + F34990FA2EE121490039E939 /* NRLabel.swift */, + ); + path = View; + sourceTree = ""; + }; + 0398109C2ED0697A0006E317 /* C */ = { + isa = PBXGroup; + children = ( + 039810922ED062CE0006E317 /* NRHomeViewController.swift */, + 0398109F2ED06B7C0006E317 /* NRHomeNovelViewController.swift */, + 039810A32ED072380006E317 /* NRHomeNovelListViewController.swift */, + F34991192EE188E40039E939 /* NRHomeNovelNewViewController.swift */, + 0373D94E2ED58A1E0017DCC7 /* NRSearchViewController.swift */, + ); + path = C; + sourceTree = ""; + }; + 0398109D2ED069840006E317 /* V */ = { + isa = PBXGroup; + children = ( + 039810A12ED070400006E317 /* NRHomeNovelHeaderView.swift */, + 039810AB2ED3EF640006E317 /* NRHomeNovelHeaderContentView.swift */, + 039810AD2ED3F3400006E317 /* NRHomeNovelMustReadTodayView.swift */, + 039810B32ED428F20006E317 /* NRHomeNovelNewArrivalsView.swift */, + 039810B72ED431780006E317 /* NRHomeNovelReadWhatView.swift */, + 039810BD2ED44C210006E317 /* NRHomeNovelHotGridView.swift */, + 039810BF2ED44D990006E317 /* NRHomeNovelNextView.swift */, + 039810C32ED459440006E317 /* NRHomeNovelHotTagView.swift */, + 039810AF2ED3F50C0006E317 /* NRHomeNovelMustReadTodayCell.swift */, + 039810B52ED42D840006E317 /* NRHomeNovelNewArrivalsCell.swift */, + 039810B92ED4377E0006E317 /* NRHomeNovelReadWhatCell.swift */, + 039810C12ED456560006E317 /* NRHomeNovelNextViewCell.swift */, + 039810C52ED45AE30006E317 /* NRHomeNovelHotTagCell.swift */, + 039810A72ED3E7DB0006E317 /* NRHomeNovelListCell.swift */, + 039810D12ED54F190006E317 /* NRHomeNovelListTextCell.swift */, + 039810CF2ED54D370006E317 /* NRHomeCategoryTagView.swift */, + 0373D9532ED58AF00017DCC7 /* NRSearchInputView.swift */, + 0373D9552ED5933F0017DCC7 /* NRSearchHomeView.swift */, + 0373D9572ED5935D0017DCC7 /* NRSearchRecordView.swift */, + 0373D9592ED593D50017DCC7 /* NRSearchRecordCell.swift */, + 0373D95D2ED59C430017DCC7 /* NRSearchResultView.swift */, + 0373D95F2ED59DA10017DCC7 /* NRSearchResultCell.swift */, + F34990BC2EDEC24E0039E939 /* NRStarGradeView.swift */, + ); + path = V; + sourceTree = ""; + }; + 0398109E2ED069890006E317 /* M */ = { + isa = PBXGroup; + children = ( + 039810BB2ED43C8E0006E317 /* NRReadWhatViewTransformer.swift */, + F34990B82EDEB1620039E939 /* NRHomeNovelModuleItem.swift */, + ); + path = M; + sourceTree = ""; + }; + 039810C82ED469BC0006E317 /* WaterfallFlowLayout */ = { + isa = PBXGroup; + children = ( + 039810C92ED469D50006E317 /* NRWaterfallFlowLayout.swift */, + ); + path = WaterfallFlowLayout; + sourceTree = ""; + }; + 7508E82FE7A8A9ED3762CFF3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 43E8F76284854E8FD006C875 /* Pods_ReaderHive.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E0C2D753F0B7D561FB16D3E3 /* Pods */ = { + isa = PBXGroup; + children = ( + C3BEE224CB3F55939653D26D /* Pods-NovelReader.debug.xcconfig */, + 455BEF77457E224E14EB6CCA /* Pods-NovelReader.release.xcconfig */, + 3258B4947CC1FF8C380F3090 /* Pods-ReaderHive.debug.xcconfig */, + 33A96151B23BA2ED4F2CCF56 /* Pods-ReaderHive.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + F34348A92ED5B59C00AA7E70 /* Explore */ = { + isa = PBXGroup; + children = ( + F34348AD2ED5B83000AA7E70 /* VC */, + F34348AC2ED5B82B00AA7E70 /* V */, + F34348AB2ED5B82400AA7E70 /* M */, + F34348AA2ED5B5E500AA7E70 /* VM */, + ); + path = Explore; + sourceTree = ""; + }; + F34348AA2ED5B5E500AA7E70 /* VM */ = { + isa = PBXGroup; + children = ( + F34348BC2ED68F2B00AA7E70 /* NRExploreNovelViewModel.swift */, + F34348C42ED6CA4D00AA7E70 /* NRExploreNovelMenuDataSource.swift */, + ); + path = VM; + sourceTree = ""; + }; + F34348AB2ED5B82400AA7E70 /* M */ = { + isa = PBXGroup; + children = ( + F34348BA2ED5CD8100AA7E70 /* NRExploreNovelMenuItem.swift */, + ); + path = M; + sourceTree = ""; + }; + F34348AC2ED5B82B00AA7E70 /* V */ = { + isa = PBXGroup; + children = ( + F34348B22ED5BB6100AA7E70 /* NRExploreNovelMenuView.swift */, + F34348B82ED5C7E400AA7E70 /* NRExploreNovelMenuCell.swift */, + F34348C02ED693E900AA7E70 /* NRExploreNovelGenresCell.swift */, + F34348C82ED6CDD900AA7E70 /* NRExploreNovelContentListCell.swift */, + F34348CC2ED6DD0900AA7E70 /* NRNovelGenresCell.swift */, + ); + path = V; + sourceTree = ""; + }; + F34348AD2ED5B83000AA7E70 /* VC */ = { + isa = PBXGroup; + children = ( + F34348AE2ED5B85300AA7E70 /* NRExploreViewController.swift */, + F34348B02ED5B9A400AA7E70 /* NRExploreNovelViewController.swift */, + F34348BE2ED691C100AA7E70 /* NRExploreNovelGenresViewController.swift */, + F34348C22ED6A20700AA7E70 /* NRExploreNovelContentViewController.swift */, + F34348C62ED6CCBC00AA7E70 /* NRExploreNovelContentListViewController.swift */, + F34348CA2ED6DADA00AA7E70 /* NRNovelGenresViewController.swift */, + ); + path = VC; + sourceTree = ""; + }; + F34348CE2ED6E0BB00AA7E70 /* MyList */ = { + isa = PBXGroup; + children = ( + F34348D12ED6E0D700AA7E70 /* VC */, + F34348D02ED6E0D400AA7E70 /* V */, + F34348CF2ED6E0CC00AA7E70 /* M */, + ); + path = MyList; + sourceTree = ""; + }; + F34348CF2ED6E0CC00AA7E70 /* M */ = { + isa = PBXGroup; + children = ( + ); + path = M; + sourceTree = ""; + }; + F34348D02ED6E0D400AA7E70 /* V */ = { + isa = PBXGroup; + children = ( + F34348D62ED6E7C600AA7E70 /* NRMyListNovelCell.swift */, + ); + path = V; + sourceTree = ""; + }; + F34348D12ED6E0D700AA7E70 /* VC */ = { + isa = PBXGroup; + children = ( + F34348D22ED6E0F400AA7E70 /* NRMyListViewController.swift */, + F34348D42ED6E16500AA7E70 /* NRMyListNovelViewController.swift */, + ); + path = VC; + sourceTree = ""; + }; + F34348EE2ED82F7100AA7E70 /* Reader */ = { + isa = PBXGroup; + children = ( + F34348F12ED8388F00AA7E70 /* NRNovelReadTopView.swift */, + F34349232EDD3C2400AA7E70 /* NRNovelReadBottomView.swift */, + F34348FA2ED8560300AA7E70 /* NRNovelReadContentTopView.swift */, + F34348FC2ED8561300AA7E70 /* NRNovelReadContentBottomView.swift */, + F34348FE2ED85BF200AA7E70 /* NRReadBatteryView.swift */, + F34349092ED96EE600AA7E70 /* NRNovelReadView.swift */, + F343490D2ED9A5D000AA7E70 /* NRNovelReadMoreView.swift */, + F34349132EDA9AE900AA7E70 /* NRNovelReadSettingView.swift */, + F34349172EDAA02900AA7E70 /* NRNovelReadSettingItemView.swift */, + F34349152EDA9FC700AA7E70 /* NRReadSettingBrightnessView.swift */, + F34349192EDAC2E500AA7E70 /* NRReadSettingThemeView.swift */, + F34349212EDD227A00AA7E70 /* NRReadSettingSpacingView.swift */, + F34349292EDD93AD00AA7E70 /* NRReadSettingFontView.swift */, + F34349252EDD6E6600AA7E70 /* NRNovelReaderCatalogView.swift */, + F34349272EDD815D00AA7E70 /* NRNovelCatalogCell.swift */, + F34990C62EDFCE500039E939 /* NRNovelReadGradeView.swift */, + F34990FC2EE124CF0039E939 /* NRNovelReadStarGradeView.swift */, + F34990F82EE118FC0039E939 /* NRNovelReadFinishHeaderView.swift */, + ); + path = Reader; + sourceTree = ""; + }; + F34348F32ED848B300AA7E70 /* Read */ = { + isa = PBXGroup; + children = ( + F34348DA2ED6F80A00AA7E70 /* NRNovelReaderViewController.swift */, + F34348F62ED84B0900AA7E70 /* NRNovelReaderViewController+Page.swift */, + F34348F42ED848EC00AA7E70 /* NRNovelReadPageViewController.swift */, + F34990F42EE0346B0039E939 /* NRNovelReadBaseViewController.swift */, + F34348F82ED855AA00AA7E70 /* NRNovelReadContentViewController.swift */, + F34990F22EE02FD60039E939 /* NRNovelReadFinishViewController.swift */, + ); + path = Read; + sourceTree = ""; + }; + F34349062ED9456100AA7E70 /* NovelTool */ = { + isa = PBXGroup; + children = ( + F34348EC2ED82B6300AA7E70 /* NRNovelReadSetManager.swift */, + F34348EA2ED82B4100AA7E70 /* NRNovelReadSet.swift */, + F34349022ED943C300AA7E70 /* NRReadParser.swift */, + F34349072ED945DA00AA7E70 /* NRCoreText.swift */, + F343491D2EDAD0AA00AA7E70 /* NRReadTheme.swift */, + ); + path = NovelTool; + sourceTree = ""; + }; + F34990C02EDFCD180039E939 /* Me */ = { + isa = PBXGroup; + children = ( + F34990C32EDFCD2E0039E939 /* VC */, + F34990C22EDFCD2A0039E939 /* V */, + F34990C12EDFCD230039E939 /* M */, + ); + path = Me; + sourceTree = ""; + }; + F34990C12EDFCD230039E939 /* M */ = { + isa = PBXGroup; + children = ( + F34991042EE165EA0039E939 /* NRMeItem.swift */, + F3B8593B2EE677170095A9CC /* NRLanguageModel.swift */, + ); + path = M; + sourceTree = ""; + }; + F34990C22EDFCD2A0039E939 /* V */ = { + isa = PBXGroup; + children = ( + F34990FE2EE158790039E939 /* NRMeCell.swift */, + F34991002EE1593A0039E939 /* NRMeHeaderView.swift */, + F3B859322EE66DD10095A9CC /* NRMeCoinsContentView.swift */, + F3B859342EE66F530095A9CC /* NRMeCoinsView.swift */, + F34991082EE169C60039E939 /* NRAboutHeaderView.swift */, + F349910A2EE16B520039E939 /* NRAboutCell.swift */, + F34991172EE1780A0039E939 /* NRNovelHistoryCell.swift */, + F3B859382EE676610095A9CC /* NRLanguageCell.swift */, + ); + path = V; + sourceTree = ""; + }; + F34990C32EDFCD2E0039E939 /* VC */ = { + isa = PBXGroup; + children = ( + F34990C42EDFCD4A0039E939 /* NRMeViewController.swift */, + F34991062EE167E80039E939 /* NRAboutViewController.swift */, + F34991132EE175E30039E939 /* NRHistoryViewController.swift */, + F34991152EE176640039E939 /* NRNovelHistoryViewController.swift */, + F3B859362EE6750B0095A9CC /* NRLanguageViewController.swift */, + ); + path = VC; + sourceTree = ""; + }; + F34990EF2EE00F460039E939 /* KeyedArchiver */ = { + isa = PBXGroup; + children = ( + F34990F02EE00F550039E939 /* NRKeyedArchiver.swift */, + ); + path = KeyedArchiver; + sourceTree = ""; + }; + F349910C2EE1702F0039E939 /* WebView */ = { + isa = PBXGroup; + children = ( + F349910D2EE1707C0039E939 /* NRWebViewController.swift */, + F349910F2EE170850039E939 /* NRWebViewController+Script.swift */, + F34991112EE170DD0039E939 /* NRWebView.swift */, + ); + path = WebView; + sourceTree = ""; + }; + F349911D2EE26B020039E939 /* Alert */ = { + isa = PBXGroup; + children = ( + F34991222EE26EAC0039E939 /* NRAlert.swift */, + F349911E2EE26C350039E939 /* NRAlertWindowManager.swift */, + F34991202EE26C660039E939 /* NRBaseAlert.swift */, + ); + path = Alert; + sourceTree = ""; + }; + F3B8593A2EE676B30095A9CC /* LocalizedManager */ = { + isa = PBXGroup; + children = ( + F3B8593F2EE6787E0095A9CC /* NRLocalizedManager.swift */, + F3B8593D2EE677740095A9CC /* NRLocalizedModel.swift */, + ); + path = LocalizedManager; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 03980F642ED009E30006E317 /* ReaderHive */ = { + isa = PBXNativeTarget; + buildConfigurationList = 03980F782ED009E40006E317 /* Build configuration list for PBXNativeTarget "ReaderHive" */; + buildPhases = ( + 01EC099A86725EFCFF2C3941 /* [CP] Check Pods Manifest.lock */, + 03980F612ED009E30006E317 /* Sources */, + 03980F622ED009E30006E317 /* Frameworks */, + 03980F632ED009E30006E317 /* Resources */, + CA7F5C24E1300D43A77AF33C /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ReaderHive; + productName = NovelReader; + productReference = 03980F652ED009E30006E317 /* ReaderHive.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 03980F5D2ED009E30006E317 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + CLASSPREFIX = NR; + LastSwiftUpdateCheck = 2600; + LastUpgradeCheck = 2600; + TargetAttributes = { + 03980F642ED009E30006E317 = { + CreatedOnToolsVersion = 26.0.1; + }; + }; + }; + buildConfigurationList = 03980F602ED009E30006E317 /* Build configuration list for PBXProject "ReaderHive" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 03980F5C2ED009E30006E317; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 03980F662ED009E30006E317 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 03980F642ED009E30006E317 /* ReaderHive */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 03980F632ED009E30006E317 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 03980F8A2ED009EB0006E317 /* Assets.xcassets in Resources */, + 039810732ED053BE0006E317 /* Localizable.strings in Resources */, + 03980F8C2ED009EB0006E317 /* LaunchScreen.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 01EC099A86725EFCFF2C3941 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ReaderHive-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + CA7F5C24E1300D43A77AF33C /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ReaderHive/Pods-ReaderHive-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ReaderHive/Pods-ReaderHive-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReaderHive/Pods-ReaderHive-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 03980F612ED009E30006E317 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F34991252EE27DB60039E939 /* NRGradientButton.swift in Sources */, + F34348FD2ED8561300AA7E70 /* NRNovelReadContentBottomView.swift in Sources */, + F34348C52ED6CA4D00AA7E70 /* NRExploreNovelMenuDataSource.swift in Sources */, + F343492A2EDD93AD00AA7E70 /* NRReadSettingFontView.swift in Sources */, + 039810982ED066B20006E317 /* NRTool.swift in Sources */, + F34991272EE2826D0039E939 /* NRNovelReadViewModel+Data.swift in Sources */, + F34348CD2ED6DD0900AA7E70 /* NRNovelGenresCell.swift in Sources */, + F34348FF2ED85BF200AA7E70 /* NRReadBatteryView.swift in Sources */, + F3B8593C2EE677170095A9CC /* NRLanguageModel.swift in Sources */, + 039810BA2ED4377E0006E317 /* NRHomeNovelReadWhatCell.swift in Sources */, + F34348C72ED6CCBC00AA7E70 /* NRExploreNovelContentListViewController.swift in Sources */, + 039810B82ED431780006E317 /* NRHomeNovelReadWhatView.swift in Sources */, + F34349142EDA9AE900AA7E70 /* NRNovelReadSettingView.swift in Sources */, + F34348BD2ED68F2B00AA7E70 /* NRExploreNovelViewModel.swift in Sources */, + F34991102EE1708C0039E939 /* NRWebViewController+Script.swift in Sources */, + F34348B12ED5B9A400AA7E70 /* NRExploreNovelViewController.swift in Sources */, + F34348BF2ED691C100AA7E70 /* NRExploreNovelGenresViewController.swift in Sources */, + 039810B02ED3F50C0006E317 /* NRHomeNovelMustReadTodayCell.swift in Sources */, + 0373D9452ED57B7B0017DCC7 /* NRNovelDetailViewModel.swift in Sources */, + 0398107C2ED055260006E317 /* AppDelegate+Config.swift in Sources */, + F34348B52ED5C6F800AA7E70 /* NRTableView.swift in Sources */, + F34348DB2ED6F80A00AA7E70 /* NRNovelReaderViewController.swift in Sources */, + F34990B92EDEB1620039E939 /* NRHomeNovelModuleItem.swift in Sources */, + F34349032ED943C300AA7E70 /* NRReadParser.swift in Sources */, + 0398106A2ED0505D0006E317 /* NRNetworkReachableManager.swift in Sources */, + F34348C32ED6A20700AA7E70 /* NRExploreNovelContentViewController.swift in Sources */, + F34991292EE285660039E939 /* NRShowRecommendPop.swift in Sources */, + 0373D9642ED5ABBC0017DCC7 /* NREmpty.swift in Sources */, + 039810B62ED42D840006E317 /* NRHomeNovelNewArrivalsCell.swift in Sources */, + 0373D95A2ED593D50017DCC7 /* NRSearchRecordCell.swift in Sources */, + 0373D9582ED5935D0017DCC7 /* NRSearchRecordView.swift in Sources */, + F343490C2ED9751800AA7E70 /* NRReadChapterCatalogModel.swift in Sources */, + F34990C72EDFCE500039E939 /* NRNovelReadGradeView.swift in Sources */, + 039810B42ED428F20006E317 /* NRHomeNovelNewArrivalsView.swift in Sources */, + F34991012EE1593A0039E939 /* NRMeHeaderView.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 */, + 039810852ED056D70006E317 /* UserDefaults+NRAdd.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 */, + F343490E2ED9A5D000AA7E70 /* NRNovelReadMoreView.swift in Sources */, + 039810932ED062CE0006E317 /* NRHomeViewController.swift in Sources */, + 039810BC2ED43C8E0006E317 /* NRReadWhatViewTransformer.swift in Sources */, + 039810D02ED54D370006E317 /* NRHomeCategoryTagView.swift in Sources */, + F34348E32ED70D2F00AA7E70 /* NRNovelDetailHeaderView+Data.swift in Sources */, + 0398107F2ED055D10006E317 /* NRLoginManager.swift in Sources */, + F34348D52ED6E16500AA7E70 /* NRMyListNovelViewController.swift in Sources */, + F34991162EE176640039E939 /* NRNovelHistoryViewController.swift in Sources */, + F34348F52ED848EC00AA7E70 /* NRNovelReadPageViewController.swift in Sources */, + 03980F872ED009EB0006E317 /* AppDelegate.swift in Sources */, + F349910E2EE1707C0039E939 /* NRWebViewController.swift in Sources */, + F34990BB2EDEB2080039E939 /* NRHomeNovelViewModel.swift in Sources */, + F34990BD2EDEC24E0039E939 /* NRStarGradeView.swift in Sources */, + F34348C12ED693E900AA7E70 /* NRExploreNovelGenresCell.swift in Sources */, + 0373D94B2ED582EE0017DCC7 /* NRNovelAPI.swift in Sources */, + 039810832ED0563D0006E317 /* NRUserInfo.swift in Sources */, + 0373D9472ED57F3F0017DCC7 /* UINavigationBar+NRAdd.swift in Sources */, + 0373D94D2ED583A80017DCC7 /* NRNovelModel.swift in Sources */, + F34348AF2ED5B85300AA7E70 /* NRExploreViewController.swift in Sources */, + 0373D9602ED59DA10017DCC7 /* NRSearchResultCell.swift in Sources */, + 039810952ED066710006E317 /* UIScreen+NRAdd.swift in Sources */, + 039810662ED04F940006E317 /* NRUrlPath.swift in Sources */, + 03980F882ED009EB0006E317 /* SceneDelegate.swift in Sources */, + F34991232EE26EAC0039E939 /* NRAlert.swift in Sources */, + F34348B72ED5C75800AA7E70 /* NRTableViewCell.swift in Sources */, + F34349102ED9A77A00AA7E70 /* NRPanModalContentView.swift in Sources */, + 039810CE2ED47A130006E317 /* CGMutablePath+NRRoundedCorner.swift in Sources */, + F34348E72ED7F91C00AA7E70 /* NSNumber+NRAdd.swift in Sources */, + F34349122EDA84F100AA7E70 /* NRProgressView.swift in Sources */, + F349910B2EE16B520039E939 /* NRAboutCell.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 */, + F34348DD2ED6F9F900AA7E70 /* NRNovelDetailMoreLikeCell.swift in Sources */, + 039810AC2ED3EF640006E317 /* NRHomeNovelHeaderContentView.swift in Sources */, + 0398106D2ED053000006E317 /* NRDefine.swift in Sources */, + F34349282EDD815D00AA7E70 /* NRNovelCatalogCell.swift in Sources */, + 039810D22ED54F190006E317 /* NRHomeNovelListTextCell.swift in Sources */, + F343492C2EDE72F300AA7E70 /* NRHomeAPI.swift in Sources */, + F3B859372EE6750B0095A9CC /* NRLanguageViewController.swift in Sources */, + F34348E12ED70A2700AA7E70 /* NRNovelDetailHeaderView+NovelCoverInfo.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 */, + 0373D9542ED58AF00017DCC7 /* NRSearchInputView.swift in Sources */, + F34348EB2ED82B4100AA7E70 /* NRNovelReadSet.swift in Sources */, + 039810682ED050390006E317 /* NRResponseCryptor.swift in Sources */, + 039810C62ED45AE30006E317 /* NRHomeNovelHotTagCell.swift in Sources */, + F34348D92ED6F01900AA7E70 /* NRNovelDetailBottomView.swift in Sources */, + F34348CB2ED6DADA00AA7E70 /* NRNovelGenresViewController.swift in Sources */, + F3B859422EE678FB0095A9CC /* NRSettingAPI.swift in Sources */, + 0373D9522ED58A950017DCC7 /* NRSearchViewModel.swift in Sources */, + F34990BF2EDEDDCF0039E939 /* NRCategoryModel.swift in Sources */, + F34349242EDD3C2400AA7E70 /* NRNovelReadBottomView.swift in Sources */, + 039810622ED04F250006E317 /* NRNetwork.swift in Sources */, + 039810782ED054740006E317 /* NRHud.swift in Sources */, + F34991032EE160F00039E939 /* NRUserAPI.swift in Sources */, + 039810C42ED459440006E317 /* NRHomeNovelHotTagView.swift in Sources */, + F34991122EE170E20039E939 /* NRWebView.swift in Sources */, + F3B859402EE6787E0095A9CC /* NRLocalizedManager.swift in Sources */, + 039810C02ED44D990006E317 /* NRHomeNovelNextView.swift in Sources */, + 0398105E2ED0481E0006E317 /* NRNavigationController.swift in Sources */, + F34348FB2ED8560300AA7E70 /* NRNovelReadContentTopView.swift in Sources */, + 0373D9562ED5933F0017DCC7 /* NRSearchHomeView.swift in Sources */, + F3B859352EE66F530095A9CC /* NRMeCoinsView.swift in Sources */, + 0398108C2ED0584F0006E317 /* NRKeychain.swift in Sources */, + 039810A02ED06B7C0006E317 /* NRHomeNovelViewController.swift in Sources */, + F34990FB2EE121490039E939 /* NRLabel.swift in Sources */, + F3B859332EE66DD10095A9CC /* NRMeCoinsContentView.swift in Sources */, + F34348F22ED8388F00AA7E70 /* NRNovelReadTopView.swift in Sources */, + F34349182EDAA02900AA7E70 /* NRNovelReadSettingItemView.swift in Sources */, + 039810812ED056090006E317 /* NRLoginToken.swift in Sources */, + F343491A2EDAC2E500AA7E70 /* NRReadSettingThemeView.swift in Sources */, + 0373D9402ED57B1C0017DCC7 /* NRNovelDetailViewController.swift in Sources */, + 039810A62ED072820006E317 /* NRCollectionView.swift in Sources */, + 039810602ED04D950006E317 /* NRNetworkModel.swift in Sources */, + F34348C92ED6CDD900AA7E70 /* NRExploreNovelContentListCell.swift in Sources */, + F34348B32ED5BB6100AA7E70 /* NRExploreNovelMenuView.swift in Sources */, + 039810C22ED456560006E317 /* NRHomeNovelNextViewCell.swift in Sources */, + F34991092EE169C60039E939 /* NRAboutHeaderView.swift in Sources */, + 039810AE2ED3F3400006E317 /* NRHomeNovelMustReadTodayView.swift in Sources */, + F34348E92ED7FA9100AA7E70 /* NRNovelDetailRecommandViewController.swift in Sources */, + 0373D95C2ED598890017DCC7 /* UIStackView+NRAdd.swift in Sources */, + 0398107A2ED054E40006E317 /* NRToast.swift in Sources */, + 039810A82ED3E7DB0006E317 /* NRHomeNovelListCell.swift in Sources */, + F34991212EE26C660039E939 /* NRBaseAlert.swift in Sources */, + F34990FD2EE124CF0039E939 /* NRNovelReadStarGradeView.swift in Sources */, + F34991182EE1780A0039E939 /* NRNovelHistoryCell.swift in Sources */, + F34990F12EE00F5A0039E939 /* NRKeyedArchiver.swift in Sources */, + F34349162EDA9FC700AA7E70 /* NRReadSettingBrightnessView.swift in Sources */, + F34990F52EE0346B0039E939 /* NRNovelReadBaseViewController.swift in Sources */, + 039810642ED04F480006E317 /* NRTargetType.swift in Sources */, + F343491E2EDAD0AA00AA7E70 /* NRReadTheme.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 */, + 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 */, + 0373D94F2ED58A1E0017DCC7 /* NRSearchViewController.swift in Sources */, + 0398108E2ED060020006E317 /* UIFont+NRAdd.swift in Sources */, + F34348D32ED6E0F400AA7E70 /* NRMyListViewController.swift in Sources */, + 0398105C2ED047FE0006E317 /* NRTabBarController.swift in Sources */, + F34990B72EDE787A0039E939 /* UIScrollView+Refresh.swift in Sources */, + F34349262EDD6E6600AA7E70 /* NRNovelReaderCatalogView.swift in Sources */, + F34348D72ED6E7C600AA7E70 /* NRMyListNovelCell.swift in Sources */, + F34349052ED9442300AA7E70 /* NRReadPageModel.swift in Sources */, + F34990F92EE118FC0039E939 /* NRNovelReadFinishHeaderView.swift in Sources */, + F34990C52EDFCD4A0039E939 /* NRMeViewController.swift in Sources */, + F349911C2EE190590039E939 /* NRNovelDetailCatalogViewController.swift in Sources */, + F34348F02ED8381E00AA7E70 /* NRNovelReadViewModel.swift in Sources */, + F349911A2EE188E50039E939 /* NRHomeNovelNewViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 03980F812ED009EB0006E317 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 03980F802ED009EB0006E317 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + 039810712ED053BE0006E317 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 039810722ED053BE0006E317 /* en */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 03980F792ED009E40006E317 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3258B4947CC1FF8C380F3090 /* Pods-ReaderHive.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8NNUR9HPV3; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ReaderHive/Source/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ReaderHive; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.hbqinjiu.ReaderHive; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 03980F7A2ED009E40006E317 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33A96151B23BA2ED4F2CCF56 /* Pods-ReaderHive.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8NNUR9HPV3; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ReaderHive/Source/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ReaderHive; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.hbqinjiu.ReaderHive; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; + 03980F7B2ED009E40006E317 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 6XALB8RSYF; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 03980F7C2ED009E40006E317 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 6XALB8RSYF; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 03980F602ED009E30006E317 /* Build configuration list for PBXProject "ReaderHive" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 03980F7B2ED009E40006E317 /* Debug */, + 03980F7C2ED009E40006E317 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 03980F782ED009E40006E317 /* Build configuration list for PBXNativeTarget "ReaderHive" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 03980F792ED009E40006E317 /* Debug */, + 03980F7A2ED009E40006E317 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 03980F5D2ED009E30006E317 /* Project object */; +} diff --git a/ReaderHive.xcodeproj/xcshareddata/xcschemes/ReaderHive.xcscheme b/ReaderHive.xcodeproj/xcshareddata/xcschemes/ReaderHive.xcscheme new file mode 100644 index 0000000..9ed3b1e --- /dev/null +++ b/ReaderHive.xcodeproj/xcshareddata/xcschemes/ReaderHive.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReaderHive/Base/Define/NRDefine.swift b/ReaderHive/Base/Define/NRDefine.swift new file mode 100644 index 0000000..eeab36b --- /dev/null +++ b/ReaderHive/Base/Define/NRDefine.swift @@ -0,0 +1,40 @@ +// +// NRDefine.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// +import UIKit + +///app版本号 +let kNRAPPVersion: String = (Bundle.main.infoDictionary!["CFBundleShortVersionString"] as? String) ?? "0" +let kNRAPPBundleVersion: String = (Bundle.main.infoDictionary!["CFBundleVersion"] as? String) ?? "0" + +let kNRAPPBundleName: String = (Bundle.main.infoDictionary!["CFBundleName"] as? String) ?? "" +let kNRAPPName: String = (Bundle.main.infoDictionary!["CFBundleDisplayName"] as? String) ?? "" + + +#if DEBUG +public func nrPrint(message: Any? , file: String = #file, function: String = #function, line: Int = #line) { + print("\n\(Date(timeIntervalSinceNow: 8 * 60 * 60)) \(file.components(separatedBy: "/").last ?? "") \(function) \(line): \(message ?? "")") +} +#else +public func nrPrint(message: Any?) { } +#endif + +public func nr_swizzled_instanceMethod(_ prefix: String, oldClass: Swift.AnyClass!, oldSelector: String, newClass: Swift.AnyClass) { + let newSelector = prefix + "_" + oldSelector; + let originalSelector = NSSelectorFromString(oldSelector) + let swizzledSelector = NSSelectorFromString(newSelector) + + let originalMethod = class_getInstanceMethod(oldClass, originalSelector) + let swizzledMethod = class_getInstanceMethod(newClass, swizzledSelector) + + let isAdd = class_addMethod(oldClass, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!)) + + if isAdd { + class_replaceMethod(newClass, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!)) + }else { + method_exchangeImplementations(originalMethod!, swizzledMethod!) + } +} diff --git a/ReaderHive/Base/Define/NRUserDefaultsKey.swift b/ReaderHive/Base/Define/NRUserDefaultsKey.swift new file mode 100644 index 0000000..119fb72 --- /dev/null +++ b/ReaderHive/Base/Define/NRUserDefaultsKey.swift @@ -0,0 +1,13 @@ +// +// NRUserDefaultsKey.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +///登录token +let kNRLoginTokenDefaultsKey = "kNRLoginTokenDefaultsKey" +///用户信息 +let kNRUserInfoDefaultsKey = "kNRUserInfoDefaultsKey" +///阅读设置 +let kNRNovelReadSetDefaultsKey = "kNRNovelReadSetDefaultsKey" diff --git a/ReaderHive/Base/Extension/CGMutablePath+NRRoundedCorner.swift b/ReaderHive/Base/Extension/CGMutablePath+NRRoundedCorner.swift new file mode 100644 index 0000000..6a5a7ec --- /dev/null +++ b/ReaderHive/Base/Extension/CGMutablePath+NRRoundedCorner.swift @@ -0,0 +1,65 @@ +// +// CGMutablePath+NRRoundedCorner.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit + +struct NRRoundedCorner { + var topLeft:CGFloat = 0 + var topRight:CGFloat = 0 + var bottomLeft:CGFloat = 0 + var bottomRight:CGFloat = 0 + + public static let zero = NRRoundedCorner(topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0) + public init(topLeft: CGFloat, topRight:CGFloat, bottomLeft:CGFloat, bottomRight:CGFloat) { + self.topLeft = topLeft + self.topRight = topRight + self.bottomLeft = bottomLeft + self.bottomRight = bottomRight + } + static func ==(v1:NRRoundedCorner, v2:NRRoundedCorner) -> Bool { + return v1.bottomLeft == v2.bottomLeft + && v1.bottomRight == v2.bottomRight + && v1.topLeft == v2.topLeft + && v1.topRight == v2.topRight + } + static func !=(v1:NRRoundedCorner, v2:NRRoundedCorner) -> Bool { + return !(v1 == v2) + } +} + +extension CGMutablePath { + func addRadiusRectangle(_ circulars: NRRoundedCorner, rect: CGRect) { + let minX = rect.minX + let minY = rect.minY + let maxX = rect.maxX + let maxY = rect.maxY + + //获取四个圆心 + let topLeftCenterX = minX + circulars.topLeft + let topLeftCenterY = minY + circulars.topLeft + + let topRightCenterX = maxX - circulars.topRight + let topRightCenterY = minY + circulars.topRight + + let bottomLeftCenterX = minX + circulars.bottomLeft + let bottomLeftCenterY = maxY - circulars.bottomLeft + + let bottomRightCenterX = maxX - circulars.bottomRight + let bottomRightCenterY = maxY - circulars.bottomRight + + //顶 左 + addArc(center: CGPoint(x: topLeftCenterX, y: topLeftCenterY), radius: circulars.topLeft, startAngle: CGFloat.pi, endAngle: CGFloat.pi * 3 / 2, clockwise: false) + //顶右 + addArc(center: CGPoint(x: topRightCenterX, y: topRightCenterY), radius: circulars.topRight, startAngle: CGFloat.pi * 3 / 2, endAngle: 0, clockwise: false) + //底右 + addArc(center: CGPoint(x: bottomRightCenterX, y: bottomRightCenterY), radius: circulars.bottomRight, startAngle: 0, endAngle: CGFloat.pi / 2, clockwise: false) + //底左 + addArc(center: CGPoint(x: bottomLeftCenterX, y: bottomLeftCenterY), radius: circulars.bottomLeft, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi, clockwise: false) + closeSubpath(); + + } +} diff --git a/ReaderHive/Base/Extension/NSNumber+NRAdd.swift b/ReaderHive/Base/Extension/NSNumber+NRAdd.swift new file mode 100644 index 0000000..2cb1682 --- /dev/null +++ b/ReaderHive/Base/Extension/NSNumber+NRAdd.swift @@ -0,0 +1,36 @@ +// +// NSNumber+NRAdd.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/27. +// + +import UIKit + +extension NSNumber { + + func toString(maximumFractionDigits: Int = 10, minimumFractionDigits: Int? = nil, roundingMode: NumberFormatter.RoundingMode? = nil) -> String { + let formatter = NumberFormatter() + formatter.minimumIntegerDigits = 1 + formatter.maximumFractionDigits = maximumFractionDigits + if let minimumFractionDigits = minimumFractionDigits { + formatter.minimumFractionDigits = minimumFractionDigits + } + if let roundingMode = roundingMode { + formatter.roundingMode = roundingMode + } + formatter.numberStyle = .none + return formatter.string(from: self) ?? "0" + } + + func formattedNumber() -> String { + let num = self.doubleValue + + if num > 1000 { + return NSNumber(value: num / 1000).toString(maximumFractionDigits: 1) + "k" + } else { + return self.toString() + } + } + +} diff --git a/ReaderHive/Base/Extension/String+NRAdd.swift b/ReaderHive/Base/Extension/String+NRAdd.swift new file mode 100644 index 0000000..778226e --- /dev/null +++ b/ReaderHive/Base/Extension/String+NRAdd.swift @@ -0,0 +1,44 @@ +// +// String+NRAdd.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// +import UIKit +import YYCategories +import SmartCodable + +extension String: SmartCodable { + + var length:Int { return (self as NSString).length } + + func size(_ font: UIFont, _ size: CGSize) -> CGSize { + return (self as NSString).size(for: font, size: size, mode: .byWordWrapping) + } + + func numberOfLines(_ font: UIFont, _ size: CGSize) -> Int { + let size = self.size(font, size) + let lineHeight = font.lineHeight + return Int(ceil(size.height / lineHeight)) + } +} + +extension String { + /// 截取字符串 + func substring(_ range:NSRange) ->String { + return (self as NSString).substring(with: range) + } + + /// 正则搜索相关字符位置 + func matches(_ pattern:String) ->[NSTextCheckingResult] { + + if isEmpty {return []} + + do { + let regularExpression = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive) + + return regularExpression.matches(in: self, options: NSRegularExpression.MatchingOptions.reportProgress, range: NSMakeRange(0, length)) + + } catch {return []} + } +} diff --git a/ReaderHive/Base/Extension/UIFont+NRAdd.swift b/ReaderHive/Base/Extension/UIFont+NRAdd.swift new file mode 100644 index 0000000..308d6d9 --- /dev/null +++ b/ReaderHive/Base/Extension/UIFont+NRAdd.swift @@ -0,0 +1,15 @@ +// +// UIFont+NRAdd.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit + +extension UIFont { + static func font(ofSize: CGFloat, weight: Weight) -> UIFont { + return .systemFont(ofSize: ofSize, weight: weight) + } +} + diff --git a/ReaderHive/Base/Extension/UINavigationBar+NRAdd.swift b/ReaderHive/Base/Extension/UINavigationBar+NRAdd.swift new file mode 100644 index 0000000..c6557fb --- /dev/null +++ b/ReaderHive/Base/Extension/UINavigationBar+NRAdd.swift @@ -0,0 +1,53 @@ +// +// UINavigationBar+NRAdd.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit + +extension UINavigationBar { + + static let titleFont = UIFont.font(ofSize: 16, weight: .semibold) + static let titleWhiteColor = UIColor.white + static let titleBlackColor = UIColor.black + + static func defaultAppearance() -> UINavigationBarAppearance { + let navBarAppearance = UINavigationBarAppearance() + navBarAppearance.configureWithOpaqueBackground() + navBarAppearance.backgroundColor = .clear + navBarAppearance.backgroundEffect = nil + navBarAppearance.shadowColor = UIColor.clear + navBarAppearance.titleTextAttributes = [ + NSAttributedString.Key.font : UINavigationBar.titleFont, + NSAttributedString.Key.foregroundColor : UINavigationBar.titleWhiteColor + ] + return navBarAppearance + } +} + +extension UINavigationBar { + + + func nr_setTranslucent(isTranslucent: Bool) { + self.isTranslucent = isTranslucent + } + + func nr_setBackgroundColor(backgroundColor: UIColor?) { + let appearance = self.standardAppearance + appearance.backgroundColor = backgroundColor + self.standardAppearance = appearance + self.scrollEdgeAppearance = appearance + } + + func nr_setTitleTextAttributes(titleTextAttributes: [NSAttributedString.Key : Any]?) { + let appearance = self.standardAppearance + + if let titleTextAttributes = titleTextAttributes { + appearance.titleTextAttributes = titleTextAttributes + } + self.scrollEdgeAppearance = appearance + self.standardAppearance = appearance + } +} diff --git a/ReaderHive/Base/Extension/UIScreen+NRAdd.swift b/ReaderHive/Base/Extension/UIScreen+NRAdd.swift new file mode 100644 index 0000000..6dfd6be --- /dev/null +++ b/ReaderHive/Base/Extension/UIScreen+NRAdd.swift @@ -0,0 +1,48 @@ +// +// UIScreen+NRAdd.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit + +extension UIScreen { + + static var screen: UIScreen { + return UIScreen.main + } + + static var width: CGFloat { + return UIScreen.main.bounds.width + } + + static var height: CGFloat { + return UIScreen.main.bounds.height + } + + static var safeTop: CGFloat { + return NRTool.keyWindow?.safeAreaInsets.top ?? 20 + } + + static var safeBottom: CGFloat { + return NRTool.keyWindow?.safeAreaInsets.bottom ?? 0 + } + + static var navBarHeight: CGFloat { + return safeTop + 44 + } + + static var tabBarHeight: CGFloat { + return safeBottom + 49 + } + + ///屏幕宽比 + static var widthRatio: CGFloat { + return UIScreen.width / 375 + } + + static func getRatioWidth(size: CGFloat) -> CGFloat { + return self.widthRatio * size + } +} diff --git a/ReaderHive/Base/Extension/UIScrollView+Refresh.swift b/ReaderHive/Base/Extension/UIScrollView+Refresh.swift new file mode 100644 index 0000000..03fb610 --- /dev/null +++ b/ReaderHive/Base/Extension/UIScrollView+Refresh.swift @@ -0,0 +1,49 @@ +// +// UIScrollView+Refresh.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/2. +// + +import UIKit +import MJRefresh + + +extension UIScrollView { + + func nr_addRefreshHeader(insetTop: CGFloat = 0, block: (() -> Void)?) { + + + self.mj_header = MJRefreshNormalHeader(refreshingBlock: { + block?() + }) + self.mj_header?.ignoredScrollViewContentInsetTop = insetTop + } + + func nr_addRefreshFooter(insetBottom: CGFloat = 0, block: (() -> Void)?) { + self.mj_footer = MJRefreshBackNormalFooter(refreshingBlock: { + block?() + }) + + self.mj_footer?.ignoredScrollViewContentInsetBottom = insetBottom + } + + + func nr_endHeaderRefreshing() { + self.mj_header?.endRefreshing() + } + + func nr_endFooterRefreshing() { + if self.mj_footer?.state == .noMoreData { return } + self.mj_footer?.endRefreshing() + } + + ///重置没有更多 + func nr_resetNoMoreData() { + self.mj_footer?.resetNoMoreData() + } + + func nr_endRefreshingWithNoMoreData() { + self.mj_footer?.endRefreshingWithNoMoreData() + } +} diff --git a/ReaderHive/Base/Extension/UIStackView+NRAdd.swift b/ReaderHive/Base/Extension/UIStackView+NRAdd.swift new file mode 100644 index 0000000..8ec4763 --- /dev/null +++ b/ReaderHive/Base/Extension/UIStackView+NRAdd.swift @@ -0,0 +1,21 @@ +// +// UIStackView+NRAdd.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit + +extension UIStackView { + + func nr_removeAllArrangedSubview() { + let arrangedSubviews = self.arrangedSubviews + + arrangedSubviews.forEach { + self.removeArrangedSubview($0) + $0.removeFromSuperview() + } + } +} + diff --git a/ReaderHive/Base/Extension/UIView+NRAdd.swift b/ReaderHive/Base/Extension/UIView+NRAdd.swift new file mode 100644 index 0000000..b40bc20 --- /dev/null +++ b/ReaderHive/Base/Extension/UIView+NRAdd.swift @@ -0,0 +1,97 @@ +// +// UIView+NRAdd.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit + + +extension UIView { + + fileprivate struct AssociatedKeys { + static var nr_roundedCorner: Int? + static var nr_effect: Int? + } + + @objc public static func fa_Awake() { + nr_swizzled_instanceMethod("nr", oldClass: self, oldSelector: "layoutSubviews", newClass: self) + } + + @objc func nr_layoutSubviews() { + nr_layoutSubviews() + + _updateRoundedCorner() + + if let effectView = effectView, effectView.frame != self.bounds { + effectView.frame = self.bounds + } + } + +} + + +//MARK: -------------- 圆角 -------------- +extension UIView { + + + private var roundedCorner: NRRoundedCorner? { + get { + return objc_getAssociatedObject(self, &AssociatedKeys.nr_roundedCorner) as? NRRoundedCorner + } + set { + objc_setAssociatedObject(self, &AssociatedKeys.nr_roundedCorner, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + ///设置圆角 + func nr_setRoundedCorner(topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) { + //清空其它设置方法 + self.roundedCorner = NRRoundedCorner(topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight) + _updateRoundedCorner() + } + + private func _updateRoundedCorner() { + guard let roundedCorner = self.roundedCorner else { return } + let rect = self.bounds + + let path = CGMutablePath() + path.addRadiusRectangle(roundedCorner, rect: rect) + + let maskLayer = CAShapeLayer() + maskLayer.frame = self.bounds + maskLayer.path = path + self.layer.mask = maskLayer + } + +} + +//MARK: -------------- 模糊效果 -------------- +extension UIView { + private var effectView: UIVisualEffectView? { + get { + return objc_getAssociatedObject(self, &AssociatedKeys.nr_effect) as? UIVisualEffectView + } + set { + objc_setAssociatedObject(self, &AssociatedKeys.nr_effect, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + + ///添加模糊效果 + func nr_addEffectView(style: UIBlurEffect.Style = .light) { + if self.effectView == nil { + let blur = UIBlurEffect(style: style) + let effectView = UIVisualEffectView(effect: blur) + effectView.isUserInteractionEnabled = false + self.addSubview(effectView) + self.sendSubviewToBack(effectView) + + self.effectView = effectView + } + } + ///删除模糊效果 + func nr_removeEffectView() { + self.effectView?.removeFromSuperview() + self.effectView = nil + } +} diff --git a/ReaderHive/Base/Extension/UserDefaults+NRAdd.swift b/ReaderHive/Base/Extension/UserDefaults+NRAdd.swift new file mode 100644 index 0000000..3cd881a --- /dev/null +++ b/ReaderHive/Base/Extension/UserDefaults+NRAdd.swift @@ -0,0 +1,44 @@ +// +// UserDefaults+NRAdd.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit + + +extension UserDefaults { + + static func nr_setObject(_ obj: NSSecureCoding?, forKey key: String) { + let defaults = UserDefaults.standard + + guard let obj = obj else { + defaults.removeObject(forKey: key) + return + } + + do { + let data = try NSKeyedArchiver.archivedData(withRootObject: obj, requiringSecureCoding: true) + defaults.set(data, forKey: key) + } catch { + print("Error archiving object: \(error)") + } + } + + static func nr_object(forKey key: String, as type: T.Type) -> T? { + let defaults = UserDefaults.standard + + guard let data = defaults.data(forKey: key) else { + return nil + } + + do { + let object = try NSKeyedUnarchiver.unarchivedObject(ofClass: type, from: data) + return object + } catch { + print("Error unarchiving object: \(error)") + return nil + } + } +} diff --git a/ReaderHive/Base/Networking/API/NRHomeAPI.swift b/ReaderHive/Base/Networking/API/NRHomeAPI.swift new file mode 100644 index 0000000..8f08836 --- /dev/null +++ b/ReaderHive/Base/Networking/API/NRHomeAPI.swift @@ -0,0 +1,144 @@ +// +// NRHomeAPI.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/2. +// + +import UIKit +import Alamofire + +struct NRHomeAPI { + + ///首页数据 + static func requestHomeData() async -> [NRHomeNovelModuleItem]? { + + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/home/all-modules") + param.method = .get + param.isToast = true + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + if response.isSuccess { + continuation.resume(returning: response.data?.list) + } else { + continuation.resume(returning: nil) + } + } + } + } + + ///Explore接口 + static func requestRankingCollection(type: String, days: Int) async -> [NRNovelModel]? { + + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/rankingCollection") + param.method = .get + param.parameters = [ + "days" : days, + "type" : type, + "current_page" : 1, + "page_size" : 20, + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + if response.isSuccess { + continuation.resume(returning: response.data?.list) + } else { + continuation.resume(returning: nil) + } + } + } + + } + + static func requestHomeNewData(page: Int) async -> [NRNovelModel]? { + + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/newShortPlay") + param.method = .post + param.parameters = [ + "current_page" : page, + "page_size" : 20, + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + if response.isSuccess { + continuation.resume(returning: response.data?.list) + } else { + continuation.resume(returning: nil) + } + } + } + } + + static func requestNewData() async -> [NRNovelModel]? { + + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/newShortPlayNoPaginate") + param.method = .post + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + if response.isSuccess { + continuation.resume(returning: response.data?.list) + } else { + continuation.resume(returning: nil) + } + } + } + } + + static func requestCategoryList() async -> [NRCategoryModel]? { + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/getCategories") + param.method = .get + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + if response.isSuccess { + continuation.resume(returning: response.data?.list) + } else { + continuation.resume(returning: nil) + } + } + } + } + + static func requestCategoryNovel(id: String, page: Int) async -> [NRNovelModel]? { + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/videoList") + param.method = .get + param.parameters = [ + "category_id" : id, + "current_page" : page, + "page_size" : 20 + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + if response.isSuccess { + continuation.resume(returning: response.data?.list) + } else { + continuation.resume(returning: nil) + } + } + } + } + + static func requestSearchNovel(text: String) async -> [NRNovelModel]? { + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/search") + param.method = .get + param.parameters = [ + "search" : text, + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + if response.isSuccess { + continuation.resume(returning: response.data?.list) + } else { + continuation.resume(returning: nil) + } + } + } + } + +} diff --git a/ReaderHive/Base/Networking/API/NRNovelAPI.swift b/ReaderHive/Base/Networking/API/NRNovelAPI.swift new file mode 100644 index 0000000..e22a975 --- /dev/null +++ b/ReaderHive/Base/Networking/API/NRNovelAPI.swift @@ -0,0 +1,240 @@ +// +// NRNovelAPI.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit +import Alamofire + + +struct NRNovelAPI { + + static func requestDetail(_ id: String) async -> (NRNovelModel?, Int?, String?) { + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/novel/getDetails") + param.method = .get + param.parameters = [ + "short_play_id" : id, + ] + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + if response.isSuccess { + continuation.resume(returning:(response.data, response.code, response.msg)) + } else { + continuation.resume(returning:(nil, response.code, response.msg)) + } + } + } + } + + ///给小说打分 + static func requestRateScore(_ id: String, stars: CGFloat) async -> Bool { + + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/novel/rateScore") + param.isLoding = true + param.parameters = [ + "short_play_id" : id, + "stars_num" : floor(stars) + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + if response.isSuccess { + continuation.resume(returning: true) + } else { + continuation.resume(returning: false) + } + } + } + } + + ///上报阅读记录 + static func requestUploadRecord(_ id: String, chapterId: String) { + var param = NRNetwork.Parameters(path: "/novel/watchProgressReport") + param.isToast = false + param.parameters = [ + "short_play_id" : id, + "short_play_video_id" : chapterId + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + + } + } + + ///查询章节列表 + static func requestChapterCatalogList(id: String, + page: Int = 1, + pageSize: Int = 10000, + sort: String = "asc") async -> [NRReadChapterCatalogModel]? { + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/novel/getChapterList") + param.method = .get + param.parameters = [ + "short_play_id" : id, + "order_by" : sort, + "current_page" : page, + "page_size" : pageSize + ] + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + if response.isSuccess { + continuation.resume(returning: response.data?.list) + } else { + continuation.resume(returning: nil) + } + } + } + } + + ///查询章节数据 10011 金币不足 10005 提示 + static func requestChapterData(novelId: String, chapterId: String) async -> (NRReadChapterModel?, Int?) { + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/novel/getChapterInfo") + param.method = .get + param.parameters = [ + "short_play_id" : novelId, + "short_play_video_id" : chapterId, + ] + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + if response.isSuccess { + continuation.resume(returning: (response.data, response.code)) + } else { + continuation.resume(returning: (nil, response.code)) + } + } + } + } + + + static func requestDetailRecommandData() async -> [NRNovelModel]? { + + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/getDetailsRecommand") + param.method = .get + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + if response.isSuccess { + continuation.resume(returning: response.data?.list) + } else { + continuation.resume(returning: nil) + } + } + } + } + + @discardableResult + static func requestCollect(isCollect: Bool, id: String, chapterId: String? = nil) async -> Bool { + await withCheckedContinuation { continuation in + let path: String + if isCollect { + path = "/collect" + } else { + path = "/cancelCollect" + } + var param = NRNetwork.Parameters(path: path) + param.method = .post + param.isLoding = true + param.parameters = [ + "short_play_id" : id, + "video_id" : chapterId ?? 0 + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + if response.isSuccess { + continuation.resume(returning: true) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + NotificationCenter.default.post(name: NRNovelAPI.updateCollectStateNotification, object: nil, userInfo: [ + "state" : isCollect, + "id" : id, + ]) + } + + } else { + continuation.resume(returning: false) + } + } + } + } + + static func requestCollectList(page: Int) async -> [NRNovelModel]? { + + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/myCollections") + param.method = .get + param.parameters = [ + "current_page" : page, + "page_size" : 20 + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + if response.isSuccess { + continuation.resume(returning: response.data?.list) + } else { + continuation.resume(returning: nil) + } + } + } + } + + static func requestHistoryList(page: Int) async -> [NRNovelModel]? { + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/myHistorys") + param.method = .get + param.parameters = [ + "current_page" : page, + "page_size" : 20 + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + if response.isSuccess { + continuation.resume(returning: response.data?.list) + } else { + continuation.resume(returning: nil) + } + } + } + } + + static func requestShowRecommendPop(id: String) async -> NRShowRecommendPop? { + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/novel/isShowRecommendPopUp") + param.method = .get + param.isLoding = true + param.parameters = [ + "short_play_id" : id, + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + + if response.isSuccess { + continuation.resume(returning: response.data) + } else { + continuation.resume(returning: nil) + } + } + } + } + + static func requestConfirmRecommend(_ id: String) { + var param = NRNetwork.Parameters(path: "/novel/confirmRecommend") + param.method = .get + param.isLoding = false + param.isToast = false + param.parameters = [ + "short_play_id" : id, + ] + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + + + } + } + +} + + +extension NRNovelAPI { + ///更新关注状态 [ "state" : isCollect, "id" : shortPlayId,] + static let updateCollectStateNotification = Notification.Name(rawValue: "NRNovelAPI.updateCollectStateNotification") + +} diff --git a/ReaderHive/Base/Networking/API/NRSettingAPI.swift b/ReaderHive/Base/Networking/API/NRSettingAPI.swift new file mode 100644 index 0000000..551efc2 --- /dev/null +++ b/ReaderHive/Base/Networking/API/NRSettingAPI.swift @@ -0,0 +1,47 @@ +// +// NRSettingAPI.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/8. +// + +import UIKit +import Alamofire + +struct NRSettingAPI { + ///获取语言列表 + static func requestLanguageList() async -> [NRLanguageModel]? { + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/languges") + param.method = .get + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response>) in + if response.isSuccess { + continuation.resume(returning: response.data?.list) + } else { + continuation.resume(returning: nil) + } + } + } + } + + ///获取语言数据 + static func requestLocalizedData(key: String) async -> NRLocalizedModel? { + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/translates") + param.method = .get + param.parameters = [ + "lang_key" : key, + ] + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + if response.isSuccess { + continuation.resume(returning: response.data) + } else { + continuation.resume(returning: nil) + } + } + } + } + +} diff --git a/ReaderHive/Base/Networking/API/NRUserAPI.swift b/ReaderHive/Base/Networking/API/NRUserAPI.swift new file mode 100644 index 0000000..33696d4 --- /dev/null +++ b/ReaderHive/Base/Networking/API/NRUserAPI.swift @@ -0,0 +1,29 @@ +// +// NRUserAPI.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import Alamofire + +struct NRUserAPI { + + static func requestUserInfo() async -> NRUserInfo? { + + await withCheckedContinuation { continuation in + var param = NRNetwork.Parameters(path: "/customer/info") + param.method = .get + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + if response.isSuccess { + continuation.resume(returning: response.data) + } else { + continuation.resume(returning: nil) + } + } + } + } + +} diff --git a/ReaderHive/Base/Networking/NRNetwork.swift b/ReaderHive/Base/Networking/NRNetwork.swift new file mode 100644 index 0000000..1b3ab0c --- /dev/null +++ b/ReaderHive/Base/Networking/NRNetwork.swift @@ -0,0 +1,201 @@ +// +// NRNetwork.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import Moya +import SmartCodable + +class NRNetwork: NSObject { + + private static let operationQueue = OperationQueue() + private static var tokenOperation: BlockOperation? + + static let provider = MoyaProvider() + + + static func request(parameters: NRNetwork.Parameters, completion: ((_ response: NRNetwork.Response) -> Void)?) { + + if NRLoginManager.manager.token == nil { + self.requestToken(completer: nil) + } + + + if let tokenOperation = self.tokenOperation, parameters.path != "/customer/register" { + + let requestOperation = BlockOperation { + let semaphore = DispatchSemaphore(value: 0) + _request(parameters: parameters) { (response: NRNetwork.Response) in + semaphore.signal() + completion?(response) + } + semaphore.wait() + } + ///设置依赖关系 + requestOperation.addDependency(tokenOperation) + + operationQueue.addOperation(requestOperation) + } else { + _request(parameters: parameters, completion: completion) + } + } + + @discardableResult + static func _request(parameters: NRNetwork.Parameters, completion: ((_ response: NRNetwork.Response) -> Void)?) -> Cancellable { + + if parameters.isLoding { + DispatchQueue.main.async { + NRHud.show() + } + } + return provider.request(.request(parameters: parameters)) { result in + if parameters.isLoding { + DispatchQueue.main.async { + NRHud.dismiss() + } + } + guard let completion = completion else {return} + _resultDispose(parameters: parameters, result: result, completion: completion) + } + } + + private static func _resultDispose(parameters: NRNetwork.Parameters, result: Result, completion: ((_ response: NRNetwork.Response) -> Void)?) { + + switch result { + case .success(let response): + let code = response.statusCode + if code == 401 || code == 402 || code == 403 { + + if parameters.path == "/customer/register" { + var res = NRNetwork.Response() + res.code = -1 + if parameters.isToast { + DispatchQueue.main.async { + NRToast.show(text: "Error".localized) + } + } + completion?(res) + } else { + if code == 402, parameters.isToast { + NRToast.show(text: "network_error_1".localized) + } + //重新获取token + self.requestToken { token in + if token != nil { + _Concurrency.Task { + await NRLoginManager.manager.updateUserInfo() + } + } + } + + ///将请求失败数据重新请求 + if let tokenOperation = self.tokenOperation, parameters.path != "/customer/register" { + + let requestOperation = BlockOperation { + let semaphore = DispatchSemaphore(value: 0) + _request(parameters: parameters) { (response: NRNetwork.Response) in + semaphore.signal() + completion?(response) + } + semaphore.wait() + } + ///设置依赖关系 + requestOperation.addDependency(tokenOperation) + + operationQueue.addOperation(requestOperation) + } + } + + + return + } + + do { + let tempData = try response.mapString() + nrPrint(message: parameters.parameters) + nrPrint(message: parameters.path) + + let response: NRNetwork.Response = _deserialize(data: tempData) + if !response.isSuccess{ + if parameters.isToast { + NRToast.show(text: response.msg) + } + } + completion?(response) + + } catch { + var res = NRNetwork.Response() + res.code = -1 + if parameters.isToast { + DispatchQueue.main.async { + NRToast.show(text: "Error".localized) + } + } + completion?(res) + } + case .failure(let error): + nrPrint(message: error) + var res = NRNetwork.Response() + res.code = -1 + if parameters.isToast { + DispatchQueue.main.async { + NRToast.show(text: "network_error_2".localized) + } + } + completion?(res) + break + } + + } + + ///解析数据 + static private func _deserialize(data: String) -> NRNetwork.Response { + var response: NRNetwork.Response? + + let decrypted = NRResponseCryptor.decrypt(data: data) + nrPrint(message: decrypted) + response = NRNetwork.Response.deserialize(from: decrypted) + response?.rawData = decrypted + + if let response = response { + return response + } else { + var response = NRNetwork.Response() + response.code = -1 + response.msg = "Error".localized + return response + } + } +} + +extension NRNetwork { + + ///获取token + static func requestToken(completer: ((_ token: NRLoginToken?) -> Void)?) { + guard self.tokenOperation == nil else { + completer?(nil) + return + } + + self.tokenOperation = BlockOperation(block: { + let semaphore = DispatchSemaphore(value: 0) + let param = NRNetwork.Parameters(path: "/customer/register") + + DispatchQueue.main.async { + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in + if let token = response.data { + NRLoginManager.manager.setAccountToken(token) + } + do { semaphore.signal() } + self.tokenOperation = nil + completer?(response.data) + } + } + semaphore.wait() + }) + operationQueue.addOperation(self.tokenOperation!) + } +} diff --git a/ReaderHive/Base/Networking/NRNetworkModel.swift b/ReaderHive/Base/Networking/NRNetworkModel.swift new file mode 100644 index 0000000..dbf8fa6 --- /dev/null +++ b/ReaderHive/Base/Networking/NRNetworkModel.swift @@ -0,0 +1,49 @@ +// +// NRNetworkModel.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import SmartCodable +import Moya +import Alamofire + +extension NRNetwork { + + struct Parameters { + var baseURL: URL? + var parameters: [String : Any]? + var method: Moya.Method = .post + var path: String + var isLoding: Bool = false + var isToast: Bool = true + } + + struct Response: SmartCodable { + + var code: Int? + var data: T? + var msg: String? + + @IgnoredKey + var rawData: Any? + + var isSuccess: Bool { + return code == 200 + } + } + + struct List: SmartCodable { + var list: [T]? + var pagination: Pagination? + } + + struct Pagination: SmartCodable { + var current_page: Int? + var page_size: Int? + var page_total: Int? + var total_size: Int? + } +} diff --git a/ReaderHive/Base/Networking/NRNetworkReachableManager.swift b/ReaderHive/Base/Networking/NRNetworkReachableManager.swift new file mode 100644 index 0000000..4c60ed5 --- /dev/null +++ b/ReaderHive/Base/Networking/NRNetworkReachableManager.swift @@ -0,0 +1,73 @@ +// +// NRNetworkReachableManager.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import Network + +class NRNetworkReachableManager: NSObject { + + static let manager = NRNetworkReachableManager() + ///是否有网 + var isReachable: Bool? + + private var connectionType: NWInterface.InterfaceType? + private var status: NWPath.Status? + + private let monitor = NWPathMonitor() + private let queue = DispatchQueue(label: "NetworkMonitorQueue") + + func startMonitoring() { + + monitor.pathUpdateHandler = { [weak self] path in + guard let self = self else { return } + self.status = path.status + + if path.usesInterfaceType(.wifi) { + self.connectionType = .wifi + } else if path.usesInterfaceType(.cellular) { + self.connectionType = .cellular + } else if path.usesInterfaceType(.wiredEthernet) { + self.connectionType = .wiredEthernet + } else { + self.connectionType = nil + } + + + let agoReachable = self.isReachable + + if path.status == .satisfied, self.connectionType != nil { + self.isReachable = true + if agoReachable == false { + DispatchQueue.main.async { + NotificationCenter.default.post(name: NRNetworkReachableManager.networkStatusDidChangeNotification, object: nil) + } + } + + } else { + self.isReachable = false + if agoReachable == true { + DispatchQueue.main.async { + NotificationCenter.default.post(name: NRNetworkReachableManager.networkStatusDidChangeNotification, object: nil) + } + } + } + } + + + monitor.start(queue: queue) + } + + func stopMonitoring() { + monitor.cancel() + } +} + +extension NRNetworkReachableManager { + + @objc static let networkStatusDidChangeNotification = NSNotification.Name(rawValue: "NRNetworkReachableManager.networkStatusDidChangeNotification") +} + diff --git a/ReaderHive/Base/Networking/NRResponseCryptor.swift b/ReaderHive/Base/Networking/NRResponseCryptor.swift new file mode 100644 index 0000000..1c63cd2 --- /dev/null +++ b/ReaderHive/Base/Networking/NRResponseCryptor.swift @@ -0,0 +1,94 @@ +// +// NRResponseCryptor.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit + +struct NRResponseCryptor { + + static func decrypt(data: String) -> String { + guard data.hasPrefix("$") else { + return data + } + + let decryptedData = deStrBytes(data: data) + return String(data: decryptedData, encoding: .utf8) ?? "" + } + + static func deStrBytes(data: String) -> Data { + let hexData = String(data.dropFirst()) + var bytes = Data() + + var index = hexData.startIndex + while index < hexData.endIndex { + let nextIndex = hexData.index(index, offsetBy: 2, limitedBy: hexData.endIndex) ?? hexData.endIndex + let byteString = String(hexData[index.. Data { + guard !data.isEmpty else { + return data + } + + let saltLen = Int(data[data.startIndex]) + guard data.count >= 1 + saltLen else { + return data + } + + let salt = data.subdata(in: 1..<1+saltLen) + let encryptedData = data.subdata(in: 1+saltLen.. Data { + let decryptedData = cxEd(data: data) + return removeSalt(data: decryptedData, salt: salt) + } + + // 加密/解密数据(按位取反) + static func cxEd(data: Data) -> Data { + return Data(data.map { $0 ^ 0xFF }) + } + + // 从数据中移除盐值 + static func removeSalt(data: Data, salt: Data) -> Data { + guard !salt.isEmpty else { + return data + } + + var result = Data() + let saltBytes = [UInt8](salt) + let saltCount = saltBytes.count + + for (index, byte) in data.enumerated() { + let saltByte = saltBytes[index % saltCount] + let decryptedByte = calRemoveSalt(v: byte, s: saltByte) + result.append(decryptedByte) + } + return result + } + + // 计算移除盐值后的字节 + static func calRemoveSalt(v: UInt8, s: UInt8) -> UInt8 { + if v >= s { + return v - s + } else { + return UInt8(0xFF) - (s - v) + 1 + } + } +} diff --git a/ReaderHive/Base/Networking/NRTargetType.swift b/ReaderHive/Base/Networking/NRTargetType.swift new file mode 100644 index 0000000..92f827d --- /dev/null +++ b/ReaderHive/Base/Networking/NRTargetType.swift @@ -0,0 +1,90 @@ +// +// NRTargetType.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import SmartCodable +import Moya +import Alamofire +import AdSupport +import YYCategories + +enum NRTargetType { + case request(parameters: NRNetwork.Parameters) +} + +extension NRTargetType: TargetType { + var baseURL: URL { + return .init(string: NRBaseURL)! + } + + var path: String { + switch self { + case .request(let param): + return "/readerhive" + param.path + } + } + + var method: Moya.Method { + switch self { + case .request(let param): + return param.method + } + } + + var task: Moya.Task { + switch self { + case .request(let param): + let parameters = param.parameters ?? [:] + return .requestParameters(parameters: parameters, encoding: getEncoding()) + } + } + + var headers: [String : String]? { + var dic: [String : String] = [ + "system-version" : UIDevice.current.systemVersion, + "lang-key" : NRLocalizedManager.shared.currentLocalizedKey, + "idfa" : ASIdentifierManager.shared().advertisingIdentifier.uuidString, + "time-zone" : NRTargetType.timeZone(), //时区 + "brand" : "apple", //品牌 + "app-version" : kNRAPPVersion, + "app-name" : "ReaderHive", + "device-id" : NRDeviceId.shared.id, //设备id + "system-type" : "ios", + "model" : UIDevice.current.machineModelName ?? "", + "authorization" : NRLoginManager.manager.token?.token ?? "", + "device-gaid" : UIDevice.current.identifierForVendor?.uuidString ?? "", + "product-prefix" : "ReaderHive" + ] +#if DEBUG + dic["security"] = "false" +#endif + + return dic + } + +} + + +extension NRTargetType { + + var sampleData: Data { return "".data(using: String.Encoding.utf8)! } + + func getEncoding() -> ParameterEncoding { + switch self.method { + case .get, .delete: + return URLEncoding.default + default: + return JSONEncoding.default + } + } + + static func timeZone() -> String { + let timeZone = NSTimeZone.local as NSTimeZone + let timeZoneSecondsFromGMT = timeZone.secondsFromGMT / 3600 + return String(format: "GMT+0%d:00", timeZoneSecondsFromGMT) + } +} diff --git a/ReaderHive/Base/Networking/NRUrlPath.swift b/ReaderHive/Base/Networking/NRUrlPath.swift new file mode 100644 index 0000000..ee34441 --- /dev/null +++ b/ReaderHive/Base/Networking/NRUrlPath.swift @@ -0,0 +1,20 @@ +// +// NRUrlPath.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit + +let NRBaseURL = "https://api-readerhive.readerhive.net" + +let NRWebBaseURL = "https://www.readerhive.net" + + + + +///用户协议 +let kNRUserAgreementWebUrl = NRWebBaseURL + "/user_policy" +///隐私协议 +let kNRPrivacyPolicyWebUrl = NRWebBaseURL + "/private" diff --git a/ReaderHive/Base/VC/NRNavigationController.swift b/ReaderHive/Base/VC/NRNavigationController.swift new file mode 100644 index 0000000..92a23ad --- /dev/null +++ b/ReaderHive/Base/VC/NRNavigationController.swift @@ -0,0 +1,44 @@ +// +// NRNavigationController.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import FDFullscreenPopGesture + +class NRNavigationController: UINavigationController { + + override func viewDidLoad() { + super.viewDidLoad() + fd_fullscreenPopGestureRecognizer.isEnabled = true + + } + + + override func pushViewController(_ viewController: UIViewController, animated: Bool) { + if children.count > 0 { + viewController.hidesBottomBarWhenPushed = true + } + super.pushViewController(viewController, animated: animated) + } + + override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { + for (index, value) in viewControllers.enumerated() { + if index != 0 { + value.hidesBottomBarWhenPushed = true + } + } + super.setViewControllers(viewControllers, animated: animated) + } + + override var childForStatusBarStyle: UIViewController? { + return self.topViewController + } + + override var childForStatusBarHidden: UIViewController? { + return self.topViewController + } + +} diff --git a/ReaderHive/Base/VC/NRTabBarController.swift b/ReaderHive/Base/VC/NRTabBarController.swift new file mode 100644 index 0000000..7466f7e --- /dev/null +++ b/ReaderHive/Base/VC/NRTabBarController.swift @@ -0,0 +1,59 @@ +// +// NRTabBarController.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit + +class NRTabBarController: UITabBarController { + + override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = .white + + let nav1 = createNavigationController(title: "My List".localized, image: UIImage(named: "tab_bar_icon_01"), selectedImage: UIImage(named: "tab_bar_icon_01_selected"), viewController: NRMyListViewController()) + let nav2 = createNavigationController(title: "Home".localized, image: UIImage(named: "tab_bar_icon_02"), selectedImage: UIImage(named: "tab_bar_icon_02_selected"), viewController: NRHomeViewController()) + let nav3 = createNavigationController(title: "Explore".localized, image: UIImage(named: "tab_bar_icon_03"), selectedImage: UIImage(named: "tab_bar_icon_03_selected"), viewController: NRExploreViewController()) + let nav4 = createNavigationController(title: "Me".localized, image: UIImage(named: "tab_bar_icon_04"), selectedImage: UIImage(named: "tab_bar_icon_04_selected"), viewController: NRMeViewController()) + + viewControllers = [nav1, nav2, nav3, nav4] + + + let appearance = UITabBarAppearance() + appearance.backgroundColor = .white + appearance.shadowColor = .clear + appearance.shadowImage = UIImage() + appearance.stackedLayoutAppearance.normal.titleTextAttributes = [ + .font : UIFont.font(ofSize: 10, weight: .medium), + .foregroundColor : UIColor.black.withAlphaComponent(0.25) + ] + appearance.stackedLayoutAppearance.selected.titleTextAttributes = [ + .font : UIFont.font(ofSize: 10, weight: .medium), + .foregroundColor : UIColor.F_9710_D + ] + self.tabBar.standardAppearance = appearance + self.tabBar.scrollEdgeAppearance = appearance + self.tabBar.isTranslucent = false + + self.selectedIndex = 1 + } + + + override var childForStatusBarStyle: UIViewController? { + return selectedViewController + } + override var childForStatusBarHidden: UIViewController? { + return selectedViewController + } + + + private func createNavigationController(title: String, image: UIImage?, selectedImage: UIImage?, viewController: UIViewController) -> UINavigationController { + let nav = NRNavigationController(rootViewController: viewController) + nav.tabBarItem.image = image + nav.tabBarItem.selectedImage = selectedImage + nav.tabBarItem.title = title + return nav + } +} diff --git a/ReaderHive/Base/VC/NRViewController.swift b/ReaderHive/Base/VC/NRViewController.swift new file mode 100644 index 0000000..3c36cd6 --- /dev/null +++ b/ReaderHive/Base/VC/NRViewController.swift @@ -0,0 +1,123 @@ +// +// NRViewController.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import SnapKit +import JXPagingView +import JXSegmentedView + +class NRViewController: UIViewController { + + var didScrollCallback: ((_ : UIScrollView) -> Void)? + + lazy var nr_isEditing = false + + lazy var backgroundImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "bg_image_01")) + return imageView + }() + + + override func viewDidLoad() { + super.viewDidLoad() + self.edgesForExtendedLayout = .top + self.view.backgroundColor = .F_8_F_8_F_8 + + if let navi = navigationController { + if navi.visibleViewController == self { + if navi.viewControllers.count > 1 { + configNavigationBack() + } + } + } + + view.addSubview(backgroundImageView) + + backgroundImageView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + } + } + + + func handleHeaderRefresh(_ completer: (() -> Void)?) { + completer?() + } + + func handleFooterRefresh(_ completer: (() -> Void)?) { + completer?() + } + +} + +extension NRViewController: JXPagingSmoothViewListViewDelegate, JXPagingViewListViewDelegate, JXSegmentedListContainerViewListDelegate { + + func listViewDidScrollCallback(callback: @escaping (UIScrollView) -> ()) { + didScrollCallback = callback + } + + func listView() -> UIView { + return self.view + } + + func listScrollView() -> UIScrollView { + return UIScrollView() + } + + func listDidAppear() { + + } + + func listDidDisappear() { + + } +} + + +extension UIViewController { + func configNavigationBack(_ imageName: String = "arrow_left_icon_01") { + let image = UIImage(named: imageName) + + let leftBarButtonItem = UIBarButtonItem(image: image, style: .plain ,target: self,action: #selector(nr_handleNavigationBack)) + navigationItem.leftBarButtonItem = leftBarButtonItem + } + + @objc func nr_handleNavigationBack() { + self.nr_toLastViewController(animated: true) + } + + func nr_toLastViewController(animated: Bool) { + if self.navigationController != nil + { + if self.navigationController?.viewControllers.count == 1 + { + self.dismiss(animated: animated, completion: nil) + } else { + self.navigationController?.popViewController(animated: animated) + } + } + else if self.presentingViewController != nil { + self.dismiss(animated: animated, completion: nil) + } + } +} + +extension UIViewController { + + func nr_setNavigationStyle(backgroundColor: UIColor = .clear, + titleFont: UIFont = UINavigationBar.titleFont, + titleColor: UIColor = UINavigationBar.titleWhiteColor, + isTranslucent: Bool = true + ) { + self.navigationController?.navigationBar.nr_setTranslucent(isTranslucent: isTranslucent) + self.navigationController?.navigationBar.nr_setBackgroundColor(backgroundColor: backgroundColor) + self.navigationController?.navigationBar.nr_setTitleTextAttributes(titleTextAttributes: [ + NSAttributedString.Key.font : titleFont, + NSAttributedString.Key.foregroundColor : titleColor + ]) + } + +} diff --git a/ReaderHive/Base/View/NRCollectionView.swift b/ReaderHive/Base/View/NRCollectionView.swift new file mode 100644 index 0000000..7032cb6 --- /dev/null +++ b/ReaderHive/Base/View/NRCollectionView.swift @@ -0,0 +1,23 @@ +// +// NRCollectionView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit + +class NRCollectionView: UICollectionView { + + override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { + super.init(frame: frame, collectionViewLayout: layout) + self.backgroundColor = .clear + self.contentInsetAdjustmentBehavior = .never + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/ReaderHive/Base/View/NRGradientButton.swift b/ReaderHive/Base/View/NRGradientButton.swift new file mode 100644 index 0000000..cb2dc0b --- /dev/null +++ b/ReaderHive/Base/View/NRGradientButton.swift @@ -0,0 +1,44 @@ +// +// NRGradientButton.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/5. +// + +import UIKit + +class NRGradientButton: UIButton { + + override class var layerClass: AnyClass { + return CAGradientLayer.self + } + + var gradientLayer: CAGradientLayer { + return self.layer as! CAGradientLayer + } + + var locations: [NSNumber]? { + didSet { + self.gradientLayer.locations = locations + } + } + + var colors: [CGColor]? { + didSet { + self.gradientLayer.colors = colors + } + } + + var startPoint: CGPoint = .zero { + didSet { + self.gradientLayer.startPoint = startPoint + } + } + + var endPoint: CGPoint = .zero { + didSet { + self.gradientLayer.endPoint = endPoint + } + } + +} diff --git a/ReaderHive/Base/View/NRGradientView.swift b/ReaderHive/Base/View/NRGradientView.swift new file mode 100644 index 0000000..a13883c --- /dev/null +++ b/ReaderHive/Base/View/NRGradientView.swift @@ -0,0 +1,44 @@ +// +// NRGradientView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit + +class NRGradientView: UIView { + + override class var layerClass: AnyClass { + return CAGradientLayer.self + } + + var gradientLayer: CAGradientLayer { + return self.layer as! CAGradientLayer + } + + var locations: [NSNumber]? { + didSet { + self.gradientLayer.locations = locations + } + } + + var colors: [CGColor]? { + didSet { + self.gradientLayer.colors = colors + } + } + + var startPoint: CGPoint = .zero { + didSet { + self.gradientLayer.startPoint = startPoint + } + } + + var endPoint: CGPoint = .zero { + didSet { + self.gradientLayer.endPoint = endPoint + } + } + +} diff --git a/ReaderHive/Base/View/NRImageView.swift b/ReaderHive/Base/View/NRImageView.swift new file mode 100644 index 0000000..b9652e1 --- /dev/null +++ b/ReaderHive/Base/View/NRImageView.swift @@ -0,0 +1,99 @@ +// +// NRImageView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import Kingfisher + +class NRImageView: UIImageView { + + var placeholderColor = UIColor.E_0_E_0_E_0 + var placeholderImage = UIImage(named: "placeholder_image") + + private lazy var placeholderImageView: UIImageView = { + let imageView = UIImageView(image: placeholderImage) + imageView.isHidden = true + imageView.contentMode = .scaleAspectFit + return imageView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + override init(image: UIImage?) { + super.init(image: image) + _init() + } + + override init(image: UIImage?, highlightedImage: UIImage?) { + super.init(image: image, highlightedImage: highlightedImage) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + + override func awakeFromNib() { + super.awakeFromNib() + _init() + } + + func _init() { + self.contentMode = .scaleAspectFill + self.layer.masksToBounds = true + if image == nil { + self.backgroundColor = self.placeholderColor + placeholderImageView.isHidden = false + } + addSubview(placeholderImageView) + } + + override var image: UIImage? { + didSet { + if self.backgroundColor == nil && image == nil { + self.backgroundColor = self.placeholderColor + } else if image != nil { + if self.backgroundColor == self.placeholderColor { + self.backgroundColor = nil + } + } + + if image == nil { + placeholderImageView.isHidden = false + } else { + placeholderImageView.isHidden = true + } + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + placeholderImageView.frame = .init(x: 0, y: 0, width: self.bounds.width * (2 / 3), height: self.bounds.height * (2 / 3)) + placeholderImageView.center = .init(x: self.bounds.width / 2, y: self.bounds.height / 2) + } + + +} + +extension UIImageView { + func nr_setImage(_ url: String?, placeholder: UIImage? = nil, completer: ((_ image: UIImage?, _ url: URL?) -> Void)? = nil) { + + self.kf.setImage(with: URL(string: url ?? ""), placeholder: placeholder, options: nil) { result in + switch result { + case .success(let value): + completer?(value.image, value.source.url) + default : + completer?(nil, nil) + break + } + } + } +} diff --git a/ReaderHive/Base/View/NRLabel.swift b/ReaderHive/Base/View/NRLabel.swift new file mode 100644 index 0000000..d322f5b --- /dev/null +++ b/ReaderHive/Base/View/NRLabel.swift @@ -0,0 +1,51 @@ +// +// NRLabel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit + +class NRLabel: UILabel { + + var textColors: [CGColor]? + var textStartPoint: CGPoint? + var textEndPoint: CGPoint? + + + override func layoutSubviews() { + super.layoutSubviews() + let size = self.bounds.size + if let text = self.text, text.count > 0, let colors = self.textColors, let startPoint = self.textStartPoint, let endPoine = self.textEndPoint { + self.textColor = UIColor(patternImage: UIImage.nr_getGradientImage(size: size, colors: colors, startPoint: startPoint, endPoint: endPoine)) + } + } + +} + +extension UIImage { + + static func nr_getGradientImage(size: CGSize, colors: [CGColor], startPoint: CGPoint, endPoint: CGPoint) -> UIImage { + + UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) + guard let context = UIGraphicsGetCurrentContext() else{return UIImage()} + let colorSpace = CGColorSpaceCreateDeviceRGB() + ///设置渐变颜色 + let gradientRef = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: nil)! + let startPoint = CGPoint(x: size.width * startPoint.x, y: size.height * startPoint.y) + let endPoint = CGPoint(x: size.width * endPoint.x, y: size.height * endPoint.y) + context.drawLinearGradient(gradientRef, start: startPoint, end: endPoint, options: CGGradientDrawingOptions(arrayLiteral: .drawsBeforeStartLocation,.drawsAfterEndLocation)) + let gradientImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return gradientImage ?? UIImage() + } + + func nr_resized(to size: CGSize) -> UIImage { + let renderer = UIGraphicsImageRenderer(size: size) + return renderer.image { _ in + self.draw(in: CGRect(origin: .zero, size: size)) + } + } + +} diff --git a/ReaderHive/Base/View/NRPanModalContentView.swift b/ReaderHive/Base/View/NRPanModalContentView.swift new file mode 100644 index 0000000..5645940 --- /dev/null +++ b/ReaderHive/Base/View/NRPanModalContentView.swift @@ -0,0 +1,79 @@ +// +// NRPanModalContentView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/28. +// + +import UIKit +import HWPanModal + +class NRPanModalContentView: HWPanModalContentView { + + var contentHeight = UIScreen.height * (2 / 3) + + var mainScrollView: UIScrollView? + + ///更新UI contentSize发生变化时调用 + func setNeedsLayoutUpdate() { + self.panModalSetNeedsLayoutUpdate() + } + + override init(frame: CGRect) { + super.init(frame: frame) + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //MARK: HWPanModalPresentable + override func panScrollable() -> UIScrollView? { + return mainScrollView + } + + override func longFormHeight() -> PanModalHeight { + return PanModalHeightMake(.content, contentHeight) + } + + override func showDragIndicator() -> Bool { + return false + } + + override func backgroundConfig() -> HWBackgroundConfig { + let config = HWBackgroundConfig() + config.backgroundAlpha = 0.0 + return config + } + + ///点击空白处关闭 + override func allowsTapBackgroundToDismiss() -> Bool { + return true + } + + override func allowsDragToDismiss() -> Bool { + return false + } + + override func allowsPullDownWhenShortState() -> Bool { + return false + } + +// override func minVerticalVelocityToTriggerDismiss() -> CGFloat { +// return 0 +// } + + override func showsScrollableVerticalScrollIndicator() -> Bool { + return false + } + + override func springDamping() -> CGFloat { + return 1 + } + + override func cornerRadius() -> CGFloat { + return 16 + } + +} diff --git a/ReaderHive/Base/View/NRProgressView.swift b/ReaderHive/Base/View/NRProgressView.swift new file mode 100644 index 0000000..bcc89fe --- /dev/null +++ b/ReaderHive/Base/View/NRProgressView.swift @@ -0,0 +1,230 @@ +// +// NRProgressView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/29. +// + +import UIKit +import YYCategories +import YYText + +class NRProgressView: UIView { + + ///滑动开始 + var panStart: (() -> Void)? + + ///滑动中 + var panChange: ((_ progress: CGFloat) -> Void)? + + ///滑动完成回调 + var panFinish: ((_ progress: CGFloat) -> Void)? + + var progress: CGFloat = 0 { + didSet { + if !isPaning { + setNeedsDisplay() + } + } + } + + ///用来记录滑动时的当前进度 + private var tempProgress: CGFloat = 0 + + ///滑动进度 + private var panProgress: CGFloat = 0 + + var progressColor: UIColor = .black.withAlphaComponent(0.25) + var currentProgress: UIColor = .F_9710_D + + var lineWidth: CGFloat = 4 + + ///加载中状态 + var isLoading = false { + didSet { + if isLoading { + if gradientTimer == nil { + gradientTimer = Timer.scheduledTimer(timeInterval: 0.05, target: YYTextWeakProxy(target: self), selector: #selector(handleGradientTimer), userInfo: nil, repeats: true) + } + } else { + gradientTimer?.invalidate() + gradientTimer = nil + } + } + } + + var thumbImage: UIImage? + + var insets: UIEdgeInsets = .init(top: 0, left: 15, bottom: 0, right: 15) { + didSet { + self.invalidateIntrinsicContentSize() + setNeedsDisplay() + } + } + + private(set) lazy var panGesture: UIPanGestureRecognizer = { + let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(sender:))) + return pan + }() + + private(set) lazy var tagGesture: UITapGestureRecognizer = { + let tap = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(sender:))) + return tap + }() + + ///是否在滑动中 + private var isPaning: Bool = false + + private var gradientTimer: Timer? + + private var gradientValue: CGFloat = 0 + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: lineWidth + insets.top + insets.bottom) + } + + override init(frame: CGRect) { + super.init(frame: frame) +// self.backgroundColor = progressColor + self.backgroundColor = .clear + + self.addGestureRecognizer(panGesture) + self.addGestureRecognizer(tagGesture) + + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + setNeedsDisplay() + } + + @objc private func handleGradientTimer() { + gradientValue += 0.1 + if gradientValue > 1 { + gradientValue = 0 + } + setNeedsDisplay() + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + guard let context = UIGraphicsGetCurrentContext() else { return } + let width = rect.width + let thumbImageSize = self.thumbImage?.size ?? .zero + + let progressX = insets.left + thumbImageSize.width / 2 + let progressY = insets.top + let progressWidth = width - insets.left - insets.right - thumbImageSize.width + + if isLoading, !isPaning { + // 定义颜色空间 + let colorSpace = CGColorSpaceCreateDeviceRGB() + let colors: [CGColor] = [ + UIColor.clear.cgColor, + UIColor.white.cgColor, + UIColor.clear.cgColor + ] + let locations: [CGFloat] = [0.0, gradientValue, 1.0] + + guard let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: locations) else { + return + } + + let gradientRect = CGRect(x: progressX, + y: progressY, + width: progressWidth, + height: lineWidth) + + // 定义渐变的起点和终点 + let startPoint = CGPoint(x: rect.minX, y: rect.minY) + let endPoint = CGPoint(x: rect.maxX, y: rect.maxY) + + // 裁剪到渐变区域 + context.saveGState() + context.clip(to: gradientRect) + + // 绘制渐变 + context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: []) + } else { + var progress = self.progress + if self.isPaning { + progress = self.panProgress + } + if progress < 0 { + progress = 0 + } + if progress > 1 { + progress = 1 + } + + + ///绘制进度 + let progressPath = UIBezierPath(roundedRect: CGRect(x: progressX, y: progressY, width: progressWidth, height: lineWidth), cornerRadius: lineWidth / 2) + context.addPath(progressPath.cgPath) + context.setFillColor(progressColor.cgColor) + context.fillPath() + + ///绘制当前进度 + let currentPath = UIBezierPath(roundedRect: CGRect(x: progressX, y: progressY, width: progressWidth * progress, height: lineWidth), cornerRadius: lineWidth / 2) + context.addPath(currentPath.cgPath) + context.setFillColor(currentProgress.cgColor) + context.fillPath() + + if let thumbImage = thumbImage { + let size = thumbImage.size + let frame = CGRect(x: progressWidth * progress - size.width / 2 + progressX, y: progressY - size.width / 2 + lineWidth / 2, width: size.width, height: size.height) + + thumbImage.draw(in: frame) + } + } + + + } + +} + +extension NRProgressView { + + @objc func handlePanGesture(sender: UIPanGestureRecognizer) { + + switch sender.state { + case .began: + self.isPaning = true + self.tempProgress = self.progress + sender.setTranslation(CGPoint(x: 0, y: 0), in: self) + self.panStart?() + + case .changed: + let point = sender.translation(in: self) + let offsetX = point.x / (self.width - self.insets.left - self.insets.right) + self.panProgress = self.tempProgress + offsetX + if self.panProgress < 0 { + self.panProgress = 0 + } + if self.panProgress > 1 { + self.panProgress = 1 + } + self.panChange?(self.panProgress) + setNeedsDisplay() + + default: + self.isPaning = false + self.panFinish?(self.panProgress) + + self.panProgress = 0 + } + } + + @objc func handleTapGesture(sender: UITapGestureRecognizer) { + let point = sender.location(in: self) + let offsetX = (point.x - self.insets.left) / (self.width - self.insets.left - self.insets.right) + self.panFinish?(offsetX) + } +} + diff --git a/ReaderHive/Base/View/NRScrollView.swift b/ReaderHive/Base/View/NRScrollView.swift new file mode 100644 index 0000000..ae76371 --- /dev/null +++ b/ReaderHive/Base/View/NRScrollView.swift @@ -0,0 +1,21 @@ +// +// NRScrollView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit + +class NRScrollView: UIScrollView { + + override init(frame: CGRect) { + super.init(frame: frame) + self.contentInsetAdjustmentBehavior = .never + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + +} diff --git a/ReaderHive/Base/View/NRTableView.swift b/ReaderHive/Base/View/NRTableView.swift new file mode 100644 index 0000000..7efcd17 --- /dev/null +++ b/ReaderHive/Base/View/NRTableView.swift @@ -0,0 +1,56 @@ +// +// NRTableView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/25. +// + +import UIKit + +class NRTableView: UITableView, UIGestureRecognizerDelegate { + + var shouldRecognizeSimultaneously = false + + + var insetGroupedMargins: CGFloat = 16 + + + override init(frame: CGRect, style: UITableView.Style) { + super.init(frame: frame, style: style) + separatorInset = .init(top: 0, left: 16, bottom: 0, right: 16) + self.backgroundColor = .clear + self.contentInsetAdjustmentBehavior = .never + + if style == .insetGrouped { + sectionFooterHeight = 14 + sectionHeaderHeight = 0.1 + } else if style == .plain { + if #available(iOS 15.0, *) { + sectionHeaderTopPadding = 0 + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var layoutMargins: UIEdgeInsets { + set { + super.layoutMargins = newValue + } + get { + var margins = super.layoutMargins + if self.style == .insetGrouped { + margins.left = self.safeAreaInsets.left + insetGroupedMargins + margins.right = self.safeAreaInsets.right + insetGroupedMargins + } + return margins + } + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return shouldRecognizeSimultaneously + } + +} diff --git a/ReaderHive/Base/View/NRTableViewCell.swift b/ReaderHive/Base/View/NRTableViewCell.swift new file mode 100644 index 0000000..afbf0da --- /dev/null +++ b/ReaderHive/Base/View/NRTableViewCell.swift @@ -0,0 +1,49 @@ +// +// NRTableViewCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/25. +// + +import UIKit + +class NRTableViewCell: UITableViewCell { + + private(set) lazy var nr_indicatorImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "arrow_right_icon_05")) + return imageView + }() + + 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 + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + +} + +extension UITableViewCell { + + var br_tableView: UITableView? { + return self.value(forKey: "_tableView") as? UITableView + } +} + diff --git a/ReaderHive/Base/WebView/NRWebView.swift b/ReaderHive/Base/WebView/NRWebView.swift new file mode 100644 index 0000000..f029ef0 --- /dev/null +++ b/ReaderHive/Base/WebView/NRWebView.swift @@ -0,0 +1,157 @@ +// +// NRWebView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +@preconcurrency import WebKit +import YYText + +//MARK:-------------- VPWebViewDelegate -------------- +@objc protocol NRWebViewDelegate: NSObjectProtocol { + + @objc optional func nr_webView(_ webView: NRWebView, shouldStartLoadWith navigationAction: WKNavigationAction) -> Bool + + @objc optional func nr_webViewDidStartLoad(_ webView: NRWebView) + + @objc optional func nr_webViewDidFinishLoad(_ webView: NRWebView) + + @objc optional func nr_webView(_ webView: NRWebView, didFailLoadWithError error: Error) + + ///进度 + @objc optional func nr_webView(webView: NRWebView, didChangeProgress progress: CGFloat) + ///标题 + @objc optional func nr_webView(webView: NRWebView, didChangeTitle title: String) + + ///web交互用 + @objc optional func nr_userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) + +} + +class NRWebView: WKWebView { + + weak var delegate: NRWebViewDelegate? + + private(set) var scriptMessageHandlerArray: [String] = [ + kNRWebMessageAPP, + kNRWebMessageOpenFeedbackList, + kNRWebMessageOpenFeedbackDetail, + kNRWebMessageOpenPhotoPicker, + kNRWebMessageAccountDeletionFinish, + ] + + + deinit { + self.removeObserver(self, forKeyPath: "estimatedProgress") + self.removeObserver(self, forKeyPath: "title") + + } + + override init(frame: CGRect, configuration: WKWebViewConfiguration) { + super.init(frame: frame, configuration: configuration) + addScriptMessageHandler() + _setupInit() + } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + private func _setupInit() { + + self.isOpaque = false + self.navigationDelegate = self + self.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil) + self.addObserver(self, forKeyPath: "title", options: .new, context: nil) + + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if object as? NRWebView == self { + if keyPath == "estimatedProgress", let progress = change?[NSKeyValueChangeKey.newKey] as? CGFloat { + self.delegate?.nr_webView?(webView: self, didChangeProgress: progress) + } else if keyPath == "title", let title = change?[NSKeyValueChangeKey.newKey] as? String { + self.delegate?.nr_webView?(webView: self, didChangeTitle: title) + } + } + } + + func load(urlStr: String) { + guard let url = URL(string: urlStr) else { return } + let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 30) + self.load(request) + } + + func removeScriptMessageHandler() { + self.scriptMessageHandlerArray.forEach{ + configuration.userContentController.removeScriptMessageHandler(forName: $0) + } + } + func addScriptMessageHandler() { + self.scriptMessageHandlerArray.forEach{ + configuration.userContentController.add(YYTextWeakProxy(target: self) as! WKScriptMessageHandler, name: $0) + } + } + + +} + + +//MARK:-------------- WKNavigationDelegate -------------- +extension NRWebView: WKNavigationDelegate { + + func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { + + decisionHandler(.allow); + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + + if let url = navigationAction.request.url, + url.scheme != "http", + url.scheme != "https" + { + UIApplication.shared.open(url) + decisionHandler(.cancel) + return + } + + if let result = self.delegate?.nr_webView?(self, shouldStartLoadWith: navigationAction) { + if result { + decisionHandler(.allow) + } else { + decisionHandler(.cancel) + } + } else { + decisionHandler(.allow) + } + } + + func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { + self.delegate?.nr_webViewDidStartLoad?(self) + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + self.delegate?.nr_webViewDidFinishLoad?(self) + } + + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + self.delegate?.nr_webView?(self, didFailLoadWithError: error) + } + + func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { + self.delegate?.nr_webView?(self, didFailLoadWithError: error) + } + + +} + +//MARK:-------------- WKScriptMessageHandler -------------- +extension NRWebView: WKScriptMessageHandler { + + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + self.delegate?.nr_userContentController?(userContentController, didReceive: message) + } + +} + diff --git a/ReaderHive/Base/WebView/NRWebViewController+Script.swift b/ReaderHive/Base/WebView/NRWebViewController+Script.swift new file mode 100644 index 0000000..9a56e80 --- /dev/null +++ b/ReaderHive/Base/WebView/NRWebViewController+Script.swift @@ -0,0 +1,28 @@ +// +// NRWebViewController+Script.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import WebKit + +///APP交互 +let kNRWebMessageAPP = "js2app" +///打开反馈列表 +let kNRWebMessageOpenFeedbackList = "openFeedbackList" +///打开反馈详情 +let kNRWebMessageOpenFeedbackDetail = "openFeedbackDetail" +///打开相册 +let kNRWebMessageOpenPhotoPicker = "openPhotoPicker" +///打开相册 +let kNRWebMessageAccountDeletionFinish = "accountLogout" + + +extension NRWebViewController { + + func nr_webViewUserContentController(didReceive message: WKScriptMessage) { + + } +} diff --git a/ReaderHive/Base/WebView/NRWebViewController.swift b/ReaderHive/Base/WebView/NRWebViewController.swift new file mode 100644 index 0000000..41d3f22 --- /dev/null +++ b/ReaderHive/Base/WebView/NRWebViewController.swift @@ -0,0 +1,111 @@ +// +// NRWebViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import WebKit +import SnapKit + +class NRWebViewController: NRViewController { + + var webUrl: String? + + var needAutoRefresh = true + + private(set) lazy var webView: NRWebView = { + let controller = WKUserContentController() + + let config = WKWebViewConfiguration() + config.userContentController = controller + config.preferences.javaScriptEnabled = true + /** 默认是不能通过JS自动打开窗口的,必须通过用户交互才能打开 */ + config.preferences.javaScriptCanOpenWindowsAutomatically = true + let webView = NRWebView(frame: self.view.bounds, configuration: config) + webView.delegate = self + return webView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.edgesForExtendedLayout = .top + self.backgroundImageView.isHidden = true + configNavigationBack("arrow_left_icon_05") + + _setupUI() + + if let url = webUrl { + self.load(webUrl: url) + } + + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + self.nr_setNavigationStyle(titleColor: UINavigationBar.titleBlackColor) + } + + func load(webUrl: String) { + let str: String = webUrl + + guard let url = URL(string: str) else { return } + let request = URLRequest(url: url, timeoutInterval: 30) + + self.webView.load(request) + } + + func reload() { + self.webView.reload() + } + +} + +extension NRWebViewController { + + private func _setupUI() { + self.view.addSubview(webView) + + self.webView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(0) + make.right.equalToSuperview().offset(0) + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + make.bottom.equalToSuperview().offset(0) + } + } + + +} + +//MARK: -------------- VPWebViewDelegate -------------- +extension NRWebViewController: NRWebViewDelegate { + + func nr_webView(_ webView: NRWebView, shouldStartLoadWith navigationAction: WKNavigationAction) -> Bool { + self.webView.isHidden = false + return true + } + + func nr_webViewDidStartLoad(_ webView: NRWebView) { + NRHud.show(containerView: self.view) + } + + func nr_webView(webView: NRWebView, didChangeTitle title: String) { + + } + + func nr_webViewDidFinishLoad(_ webView: NRWebView) { + self.webView.isHidden = false + NRHud.dismiss() + } + + func nr_webView(_ webView: NRWebView, didFailLoadWithError error: any Error) { + NRHud.dismiss() + } + + func nr_userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + nr_webViewUserContentController(didReceive: message) + } + +} diff --git a/ReaderHive/Class/Explore/M/NRExploreNovelMenuItem.swift b/ReaderHive/Class/Explore/M/NRExploreNovelMenuItem.swift new file mode 100644 index 0000000..141b34d --- /dev/null +++ b/ReaderHive/Class/Explore/M/NRExploreNovelMenuItem.swift @@ -0,0 +1,47 @@ +// +// NRExploreNovelMenuItem.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/25. +// + +import UIKit + +struct NRExploreNovelMenuItem { + + enum ItemType: String { + case genres + case trending = "trending" + case topRated = "score" + case collected = "collect" + case bestSellers = "sellers" + +// func getTitle() -> String { +// switch self { +// case .genres: +// return "Genres" +// +// case .trending: +// return "Trending".localized +// +// case .topRated: +// return "Top Rated".localized +// +// case .collected: +// return "Most Collected".localized +// +// case .bestSellers: +// return "Best Sellers".localized +// +// default: +// return "" +// } +// } + } + + var type: ItemType? + var title: String? + var icon: UIImage? + var selectedIcon: UIImage? + +} diff --git a/ReaderHive/Class/Explore/V/NRExploreNovelContentListCell.swift b/ReaderHive/Class/Explore/V/NRExploreNovelContentListCell.swift new file mode 100644 index 0000000..81159cf --- /dev/null +++ b/ReaderHive/Class/Explore/V/NRExploreNovelContentListCell.swift @@ -0,0 +1,199 @@ +// +// NRExploreNovelContentListCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit + +class NRExploreNovelContentListCell: UICollectionViewCell { + + + var row: Int = 0 { + didSet { + let num = row + 1 + numLabel.text = "\(num)" + + if num < 4 { + numLabel.textColor = .white + } else { + numLabel.textColor = .black.withAlphaComponent(0.25) + } + switch num { + case 1: + numBgView.image = UIImage(named: "num_1_bg_icon") + case 2: + numBgView.image = UIImage(named: "num_2_bg_icon") + case 3: + numBgView.image = UIImage(named: "num_3_bg_icon") + default: + numBgView.image = UIImage(named: "num_4_bg_icon") + } + } + } + + var menuItem: NRExploreNovelMenuItem? { + didSet { + switch menuItem?.type { + case .trending: + countView.configuration?.image = UIImage(named: "hot_icon_01") + + case .topRated: + countView.configuration?.image = UIImage(named: "star_icon_01") + + case .collected: + countView.configuration?.image = UIImage(named: "collected_icon_01") + + default: + break + } + + if menuItem?.type == .bestSellers { + countView.isHidden = true + } else { + countView.isHidden = false + countView.setNeedsUpdateConfiguration() + } + } + } + + var model: NRNovelModel? { + didSet { + coverImageView.nr_setImage(model?.image_url) + nameLabel.text = model?.name + categoryLabel.text = model?.category?.first + countView.setNeedsUpdateConfiguration() + } + } + + private var grade: CGFloat { + switch menuItem?.type { + case .trending: + return CGFloat(model?.watch_total ?? 0) + + case .topRated: + return model?.rate ?? 0 + + case .collected: + return CGFloat(model?.collect_total ?? 0) + + default: + return 0 + } + } + + lazy var numBgView: UIImageView = { + let imageView = UIImageView() + return imageView + }() + + lazy var numLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .medium) + return label + }() + + lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + return imageView + }() + + lazy var nameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .medium) + label.textColor = .black + label.numberOfLines = 2 + return label + }() + + lazy var categoryLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 10, weight: .regular) + label.textColor = .black.withAlphaComponent(0.25) + return label + }() + + lazy var countView: UIButton = { + var configuration = UIButton.Configuration.plain() + configuration.contentInsets = .zero + configuration.imagePadding = 4 + configuration.imagePlacement = .top + + + let button = UIButton(configuration: configuration) + button.isUserInteractionEnabled = false + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + + button.configuration?.attributedTitle = AttributedString(NSNumber(value: self.grade).formattedNumber(), attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 10, weight: .regular), + .foregroundColor : UIColor.black + ])) + + } + + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + nameLabel.text = "Shadows of Vengeance" + categoryLabel.text = "Satisfying" + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRExploreNovelContentListCell { + + private func nr_setupUI() { + contentView.addSubview(numBgView) + numBgView.addSubview(numLabel) + contentView.addSubview(coverImageView) + contentView.addSubview(nameLabel) + contentView.addSubview(categoryLabel) + contentView.addSubview(countView) + + numBgView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.centerX.equalTo(self.contentView.snp.left).offset(20) + } + + numLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.centerY.equalToSuperview().offset(-2) + } + + coverImageView.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.left.equalTo(numBgView.snp.right).offset(8) + make.width.equalTo(40) + } + + nameLabel.snp.makeConstraints { make in + make.left.equalTo(coverImageView.snp.right).offset(8) + make.centerY.equalTo(self.contentView.snp.top).offset(20) + make.right.lessThanOrEqualToSuperview().offset(-60) + } + + categoryLabel.snp.makeConstraints { make in + make.left.equalTo(nameLabel) + make.top.equalTo(nameLabel.snp.bottom).offset(5) + } + + countView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.centerX.equalTo(self.contentView.snp.right).offset(-32) + } + } + +} diff --git a/ReaderHive/Class/Explore/V/NRExploreNovelGenresCell.swift b/ReaderHive/Class/Explore/V/NRExploreNovelGenresCell.swift new file mode 100644 index 0000000..6356af0 --- /dev/null +++ b/ReaderHive/Class/Explore/V/NRExploreNovelGenresCell.swift @@ -0,0 +1,40 @@ +// +// NRExploreNovelGenresCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit + +class NRExploreNovelGenresCell: UICollectionViewCell { + + + lazy var textLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + self.contentView.layer.cornerRadius = 8 + self.contentView.layer.masksToBounds = true + self.contentView.backgroundColor = .F_2_EFEE + + contentView.addSubview(textLabel) + + textLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + make.right.lessThanOrEqualToSuperview().offset(-5) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/ReaderHive/Class/Explore/V/NRExploreNovelMenuCell.swift b/ReaderHive/Class/Explore/V/NRExploreNovelMenuCell.swift new file mode 100644 index 0000000..fd04990 --- /dev/null +++ b/ReaderHive/Class/Explore/V/NRExploreNovelMenuCell.swift @@ -0,0 +1,73 @@ +// +// NRExploreNovelMenuCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/25. +// + +import UIKit +import SnapKit + +class NRExploreNovelMenuCell: UICollectionViewCell { + + var item: NRExploreNovelMenuItem? { + didSet { + titleLabel.text = item?.title + updateState() + } + } + + var nr_isSelected: Bool = false { + didSet { + updateState() + } + } + + lazy var iconImageView = UIImageView() + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 10, weight: .regular) + label.numberOfLines = 0 + label.textAlignment = .center + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + contentView.layer.cornerRadius = 8 + contentView.layer.masksToBounds = true + contentView.layer.borderWidth = 1 + + addSubview(iconImageView) + addSubview(titleLabel) + + iconImageView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(6) + } + + titleLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(iconImageView.snp.bottom).offset(8) + make.right.lessThanOrEqualToSuperview().offset(-2) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func updateState() { + iconImageView.image = nr_isSelected ? item?.selectedIcon : item?.icon + titleLabel.textColor = nr_isSelected ? .black : .black.withAlphaComponent(0.25) + + if nr_isSelected { + contentView.backgroundColor = .white + contentView.layer.borderColor = UIColor.F_2_EFEE.cgColor + } else { + contentView.backgroundColor = .clear + contentView.layer.borderColor = UIColor.clear.cgColor + } + } + +} diff --git a/ReaderHive/Class/Explore/V/NRExploreNovelMenuView.swift b/ReaderHive/Class/Explore/V/NRExploreNovelMenuView.swift new file mode 100644 index 0000000..1d47fd6 --- /dev/null +++ b/ReaderHive/Class/Explore/V/NRExploreNovelMenuView.swift @@ -0,0 +1,101 @@ +// +// NRExploreNovelMenuView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/25. +// + +import UIKit +import SnapKit + + +class NRExploreNovelMenuView: UIView { + + override var intrinsicContentSize: CGSize { + return .init(width: 60, height: UIScreen.height) + } + + var didSelected: ((Int) -> Void)? + + weak var viewModel: NRExploreNovelViewModel? + + private(set) var selectedIndex = 0 + + private lazy var collectionViewLayout: NRWaterfallFlowLayout = { + let layout = NRWaterfallFlowLayout() + layout.delegate = self + return layout + }() + + private lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.showsVerticalScrollIndicator = false + collectionView.register(NRExploreNovelMenuCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRExploreNovelMenuView { + + private func nr_setupUI() { + addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + +} + +//AMRK: UICollectionViewDelegate UICollectionViewDataSource +extension NRExploreNovelMenuView: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRExploreNovelMenuCell + cell.item = self.viewModel?.menuDataArr[indexPath.row] + cell.nr_isSelected = indexPath.row == self.selectedIndex + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.viewModel?.menuDataArr.count ?? 0 + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard self.selectedIndex != indexPath.row else { return } + self.selectedIndex = indexPath.row + self.collectionView.reloadData() + self.didSelected?(self.selectedIndex) + } +} + +//MARK: NRWaterfallMutiSectionDelegate +extension NRExploreNovelMenuView: NRWaterfallMutiSectionDelegate { + + func heightForRowAtIndexPath(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, indexPath: IndexPath, itemWidth: CGFloat) -> CGFloat { + let item = self.viewModel?.menuDataArr[indexPath.row] + let textHeight = item?.title?.size(.font(ofSize: 10, weight: .regular), .init(width: 60, height: 100)).height ?? 0 + return textHeight + 44 + } + + func columnNumber(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, section: Int) -> Int { + return 1 + } + + func lineSpacing(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, section: Int) -> CGFloat { + return 16 + } +} diff --git a/ReaderHive/Class/Explore/V/NRNovelGenresCell.swift b/ReaderHive/Class/Explore/V/NRNovelGenresCell.swift new file mode 100644 index 0000000..92bfcd0 --- /dev/null +++ b/ReaderHive/Class/Explore/V/NRNovelGenresCell.swift @@ -0,0 +1,108 @@ +// +// NRNovelGenresCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit + +class NRNovelGenresCell: UICollectionViewCell { + + var model: NRNovelModel? { + didSet { + coverImageView.nr_setImage(model?.image_url) + titleLabel.text = model?.name + if let text = model?.categoryList?.first?.name, text.count > 0 { + self.categoryView.isHidden = false + self.categoryView.text = text + } else if let text = model?.category?.first, text.count > 0 { + self.categoryView.isHidden = false + self.categoryView.text = text + } else { + self.categoryView.isHidden = true + } + + if model?.tag_type == .new { + markImageView.isHidden = false + markImageView.image = UIImage(named: "new_icon_01") + } else if model?.tag_type == .hot { + markImageView.isHidden = false + markImageView.image = UIImage(named: "hot_icon_03") + } else { + markImageView.isHidden = true + } + } + } + + lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + imageView.layer.masksToBounds = true + return imageView + }() + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + label.numberOfLines = 2 + return label + }() + + lazy var categoryView: NRHomeCategoryTagView = { + let view = NRHomeCategoryTagView() + return view + }() + + + lazy var markImageView: UIImageView = { + let imageView = UIImageView() + return imageView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + categoryView.text = "Satisfying" + titleLabel.text = "Vanished Without a Word" + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRNovelGenresCell { + private func nr_setupUI() { + contentView.addSubview(coverImageView) + coverImageView.addSubview(markImageView) + contentView.addSubview(titleLabel) + contentView.addSubview(categoryView) + + + coverImageView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.bottom.equalToSuperview().offset(-68) + } + + markImageView.snp.makeConstraints { make in + make.left.top.equalToSuperview() + } + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview() + make.right.lessThanOrEqualToSuperview() + make.top.equalTo(coverImageView.snp.bottom).offset(8) + } + + categoryView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.bottom.equalToSuperview() + } + + } +} diff --git a/ReaderHive/Class/Explore/VC/NRExploreNovelContentListViewController.swift b/ReaderHive/Class/Explore/VC/NRExploreNovelContentListViewController.swift new file mode 100644 index 0000000..517ffab --- /dev/null +++ b/ReaderHive/Class/Explore/VC/NRExploreNovelContentListViewController.swift @@ -0,0 +1,109 @@ +// +// NRExploreNovelContentListViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit + +class NRExploreNovelContentListViewController: NRViewController { + + var menuItem: NRExploreNovelMenuItem? + + var contentType: NRExploreNovelContentViewController.ContentType = .today + + lazy var dataArr: [NRNovelModel] = [] + + 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(60)), subitems: [item]) + + let section = NSCollectionLayoutSection(group: group) + section.interGroupSpacing = 8 + section.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0) + + let configuration = UICollectionViewCompositionalLayoutConfiguration() + + let layout = UICollectionViewCompositionalLayout(section: section, configuration: configuration) + + return layout + }() + + lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.contentInset = .init(top: 0, left: 0, bottom: 10, right: 0) + collectionView.register(NRExploreNovelContentListCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.backgroundImageView.isHidden = true + self.view.backgroundColor = .clear + + nr_setupUI() + + Task { + await requestDataArr() + } + } + + + + +} + +extension NRExploreNovelContentListViewController { + + private func nr_setupUI() { + view.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(12) + } + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRExploreNovelContentListViewController: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRExploreNovelContentListCell + cell.menuItem = self.menuItem + cell.row = indexPath.row + cell.model = 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 = dataArr[indexPath.row] + let vc = NRNovelDetailViewController() + vc.novelId = model.id ?? "" + self.navigationController?.pushViewController(vc, animated: true) + } + +} + +extension NRExploreNovelContentListViewController { + + private func requestDataArr() async { + guard let type = self.menuItem?.type else { return } + guard let list = await NRHomeAPI.requestRankingCollection(type: type.rawValue, days: self.contentType.rawValue) else { return } + + self.dataArr = list + + self.collectionView.reloadData() + } + +} diff --git a/ReaderHive/Class/Explore/VC/NRExploreNovelContentViewController.swift b/ReaderHive/Class/Explore/VC/NRExploreNovelContentViewController.swift new file mode 100644 index 0000000..27825a4 --- /dev/null +++ b/ReaderHive/Class/Explore/VC/NRExploreNovelContentViewController.swift @@ -0,0 +1,166 @@ +// +// NRExploreNovelContentViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit +import JXSegmentedView + +class NRExploreNovelContentViewController: NRViewController { + + enum ContentType: Int { + case today = 1 + case week = 7 + case month = 30 + } + + var menuItem: NRExploreNovelMenuItem? + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .semibold) + label.textColor = .black + return label + }() + + lazy var moreButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + + })) + button.setImage(UIImage(named: "arrow_right_icon_03"), for: .normal) + return button + }() + + lazy var pageMenuDataSource: JXSegmentedTitleDataSource = { + let dataSource = NRExploreNovelMenuDataSource() + dataSource.titles = ["Today".localized, "This Week".localized, "This Month".localized] + dataSource.titleNormalFont = .font(ofSize: 12, weight: .regular) + dataSource.titleSelectedFont = .font(ofSize: 12, weight: .semibold) + dataSource.titleNormalColor = .black.withAlphaComponent(0.5) + dataSource.titleSelectedColor = .white + dataSource.itemSpacing = 12 + dataSource.itemWidthIncrement = 24 + dataSource.isTitleColorGradientEnabled = true + return dataSource + }() + + lazy var lineView: UIView = { + let view = UIView() + view.backgroundColor = .F_2_EFEE + return view + }() + + lazy var pageMenuView: JXSegmentedView = { + let view = JXSegmentedView() + view.dataSource = pageMenuDataSource + view.contentEdgeInsetLeft = 12 + view.contentEdgeInsetRight = 12 + view.listContainer = pageView + view.indicators = [SegmentedIndicatorView()] + return view + }() + + lazy var pageView: JXSegmentedListContainerView = { + let pageView = JXSegmentedListContainerView(dataSource: self) + return pageView + }() + + + + override func viewDidLoad() { + super.viewDidLoad() + self.backgroundImageView.isHidden = true + self.view.backgroundColor = .clear + + self.titleLabel.text = menuItem?.title + + nr_setupUI() + + } + + + +} + +extension NRExploreNovelContentViewController { + + private func nr_setupUI() { + view.addSubview(titleLabel) + view.addSubview(moreButton) + view.addSubview(pageMenuView) + view.addSubview(pageView) + view.addSubview(lineView) + + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(12) + make.centerY.equalTo(moreButton) + } + + moreButton.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-12) + make.top.equalToSuperview().offset(16) + } + + pageMenuView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalTo(moreButton.snp.bottom).offset(12) + make.height.equalTo(24) + } + + lineView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(12) + make.right.equalToSuperview().offset(-12) + make.top.equalTo(pageMenuView.snp.bottom).offset(12) + make.height.equalTo(1) + } + + pageView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(lineView.snp.bottom) + } + + } + +} + +extension NRExploreNovelContentViewController: JXSegmentedListContainerViewDataSource { + func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int { + return pageMenuDataSource.titles.count + } + + func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> any JXSegmentedListContainerViewListDelegate { + let vc = NRExploreNovelContentListViewController() + vc.menuItem = self.menuItem + if index == 0 { + vc.contentType = .today + } else if index == 1 { + vc.contentType = .week + } else if index == 2 { + vc.contentType = .month + } + return vc + } +} + + + +extension NRExploreNovelContentViewController { + + class SegmentedIndicatorView: JXSegmentedIndicatorLineView { + + override func commonInit() { + super.commonInit() + indicatorColor = .F_9710_D + + indicatorHeight = JXSegmentedViewAutomaticDimension + } + + } + + +} diff --git a/ReaderHive/Class/Explore/VC/NRExploreNovelGenresViewController.swift b/ReaderHive/Class/Explore/VC/NRExploreNovelGenresViewController.swift new file mode 100644 index 0000000..55e22bc --- /dev/null +++ b/ReaderHive/Class/Explore/VC/NRExploreNovelGenresViewController.swift @@ -0,0 +1,104 @@ +// +// NRExploreNovelGenresViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit + +class NRExploreNovelGenresViewController: NRViewController { + + + lazy var dataArr: [NRCategoryModel] = [] + + + lazy var collectionViewLayout: UICollectionViewCompositionalLayout = { + + let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1))) + + let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(48)), subitems: [item]) + group.interItemSpacing = .fixed(8) + + let section = NSCollectionLayoutSection(group: group) + section.interGroupSpacing = 8 + section.contentInsets = .init(top: 0, leading: 12, bottom: 0, trailing: 12) + + let configuration = UICollectionViewCompositionalLayoutConfiguration() + + let layout = UICollectionViewCompositionalLayout(section: section, configuration: configuration) + + return layout + }() + + + lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.contentInset = .init(top: 0, left: 0, bottom: 10, right: 0) + collectionView.register(NRExploreNovelGenresCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.backgroundImageView.isHidden = true + self.view.backgroundColor = .clear + + nr_setupUI() + + Task { + await requestDataArr() + } + } + + + +} + +extension NRExploreNovelGenresViewController { + + private func nr_setupUI() { + view.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(16) + } + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRExploreNovelGenresViewController: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRExploreNovelGenresCell + cell.textLabel.text = dataArr[indexPath.row].name + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let vc = NRNovelGenresViewController() + vc.model = self.dataArr[indexPath.row] + self.navigationController?.pushViewController(vc, animated: true) + } + +} + +extension NRExploreNovelGenresViewController { + + private func requestDataArr() async { + guard let list = await NRHomeAPI.requestCategoryList() else { return } + + self.dataArr = list + self.collectionView.reloadData() + } + +} diff --git a/ReaderHive/Class/Explore/VC/NRExploreNovelViewController.swift b/ReaderHive/Class/Explore/VC/NRExploreNovelViewController.swift new file mode 100644 index 0000000..7682676 --- /dev/null +++ b/ReaderHive/Class/Explore/VC/NRExploreNovelViewController.swift @@ -0,0 +1,109 @@ +// +// NRExploreNovelViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/25. +// + +import UIKit +import SnapKit +//import JXPagingView +import JXSegmentedView + +class NRExploreNovelViewController: NRViewController { + + + lazy var viewModel = NRExploreNovelViewModel() + + lazy var menuView: NRExploreNovelMenuView = { + let view = NRExploreNovelMenuView() + view.viewModel = self.viewModel + view.didSelected = { [weak self] index in + guard let self = self else { return } + let contentScrollView = self.pageView.contentScrollView() + contentScrollView.setContentOffset(CGPoint(x: contentScrollView.bounds.size.width*CGFloat(index), y: 0), animated: false) + self.pageView.didClickSelectedItem(at: index) + } + return view + }() + + lazy var contentView: UIView = { + let view = UIView() + view.layer.cornerRadius = 16 + view.layer.masksToBounds = true + view.layer.borderWidth = 1 + view.layer.borderColor = UIColor.F_2_EFEE.cgColor + view.backgroundColor = .white + return view + }() + + + lazy var pageView: JXSegmentedListContainerView = { + let view = JXSegmentedListContainerView(dataSource: self) + view.listCellBackgroundColor = .clear + view.contentScrollView().isScrollEnabled = false + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.backgroundImageView.isHidden = true + self.view.backgroundColor = .clear + + + + nr_setupUI() + } + + +} + +extension NRExploreNovelViewController { + + private func nr_setupUI() { + view.addSubview(menuView) + view.addSubview(contentView) + contentView.addSubview(pageView) + + menuView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview().offset(12) + make.bottom.equalToSuperview() + } + + contentView.snp.makeConstraints { make in + make.top.equalTo(menuView) + make.left.equalTo(menuView.snp.right).offset(8) + make.right.equalToSuperview().offset(-16) + make.bottom.equalToSuperview().offset(UIScreen.tabBarHeight) + } + + pageView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.bottom.equalToSuperview().offset(-UIScreen.tabBarHeight) + } + } +} + +//MARK: JXSegmentedListContainerViewDataSource +extension NRExploreNovelViewController: JXSegmentedListContainerViewDataSource { + func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int { + return self.viewModel.menuDataArr.count + } + + func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> any JXSegmentedListContainerViewListDelegate { + let item = self.viewModel.menuDataArr[index] + switch item.type { + case .genres: + return NRExploreNovelGenresViewController() + default: + let vc = NRExploreNovelContentViewController() + vc.menuItem = item + return vc + } + + } + + + +} diff --git a/ReaderHive/Class/Explore/VC/NRExploreViewController.swift b/ReaderHive/Class/Explore/VC/NRExploreViewController.swift new file mode 100644 index 0000000..ce2c66e --- /dev/null +++ b/ReaderHive/Class/Explore/VC/NRExploreViewController.swift @@ -0,0 +1,69 @@ +// +// NRExploreViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/25. +// + +import UIKit +import SnapKit + +class NRExploreViewController: NRViewController { + + + lazy var searchButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "search_icon_01"), for: .normal) + button.addAction(UIAction(handler: { [weak self] _ in + guard let self = self else { return } + let vc = NRSearchViewController() + self.navigationController?.pushViewController(vc, animated: true) + }), for: .touchUpInside) + return button + }() + + lazy var titleView = UIImageView(image: UIImage(named: "home_title_image")) + + lazy var novelVC = NRExploreNovelViewController() + + override func viewDidLoad() { + super.viewDidLoad() + + nr_setupUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + + + } + +} + +extension NRExploreViewController { + + private func nr_setupUI() { + view.addSubview(searchButton) + view.addSubview(titleView) + addChild(novelVC) + view.addSubview(novelVC.view) + + searchButton.snp.makeConstraints { make in + make.height.equalTo(44) + make.right.equalToSuperview().offset(-16) + make.top.equalTo(UIScreen.safeTop) + } + + titleView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.centerY.equalTo(searchButton) + } + + novelVC.view.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(searchButton.snp.bottom) + } + + } +} diff --git a/ReaderHive/Class/Explore/VC/NRNovelGenresViewController.swift b/ReaderHive/Class/Explore/VC/NRNovelGenresViewController.swift new file mode 100644 index 0000000..d677e4c --- /dev/null +++ b/ReaderHive/Class/Explore/VC/NRNovelGenresViewController.swift @@ -0,0 +1,144 @@ +// +// NRNovelGenresViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit +import LYEmptyView + +class NRNovelGenresViewController: NRViewController { + + var model: NRCategoryModel? + + private lazy var dataArr: [NRNovelModel] = [] + private lazy var page = 1 + + lazy var collectionViewLayout: UICollectionViewCompositionalLayout = { + let itemWidth = (UIScreen.width - 32 - 40) / 3 + let itemHeight = 150 / 100 * itemWidth + 68 + + let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1 / 3), heightDimension: .fractionalHeight(1))) + + let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(itemHeight)), subitems: [item]) + group.interItemSpacing = .fixed(20) + + let section = NSCollectionLayoutSection(group: group) + section.interGroupSpacing = 18 + section.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16) + + let configuration = UICollectionViewCompositionalLayoutConfiguration() + + let layout = UICollectionViewCompositionalLayout(section: section, configuration: configuration) + + return layout + }() + + lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.ly_emptyView = NREmpty.nr_emptyView() + collectionView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) + collectionView.nr_addRefreshHeader { [weak self] in + self?.handleHeaderRefresh(nil) + } + collectionView.nr_addRefreshFooter(insetBottom: collectionView.contentInset.bottom) { [weak self] in + self?.handleFooterRefresh(nil) + } + collectionView.register(NRNovelGenresCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.edgesForExtendedLayout = .top + self.backgroundImageView.isHidden = true + self.title = self.model?.name + + configNavigationBack("arrow_left_icon_05") + + nr_setupUI() + + Task { + await requestDataArr(page: 1) + } + } + + + 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)?) { + Task { + await requestDataArr(page: 1) + self.collectionView.nr_endHeaderRefreshing() + } + } + + override func handleFooterRefresh(_ completer: (() -> Void)?) { + Task { + await requestDataArr(page: self.page + 1) + self.collectionView.nr_endFooterRefreshing() + } + } + +} + +extension NRNovelGenresViewController { + + private func nr_setupUI() { + view.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight + 10) + } + + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRNovelGenresViewController: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRNovelGenresCell + cell.model = self.dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let model = dataArr[indexPath.row] + let vc = NRNovelDetailViewController() + vc.novelId = model.id ?? "" + self.navigationController?.pushViewController(vc, animated: true) + } + +} + +extension NRNovelGenresViewController { + + private func requestDataArr(page: Int) async { + guard let id = self.model?.id else { return } + guard let list = await NRHomeAPI.requestCategoryNovel(id: id, page: page) else { return } + + if page == 1 { + self.dataArr.removeAll() + } + self.dataArr += list + self.page = page + + self.collectionView.reloadData() + } + +} diff --git a/ReaderHive/Class/Explore/VM/NRExploreNovelMenuDataSource.swift b/ReaderHive/Class/Explore/VM/NRExploreNovelMenuDataSource.swift new file mode 100644 index 0000000..2455e46 --- /dev/null +++ b/ReaderHive/Class/Explore/VM/NRExploreNovelMenuDataSource.swift @@ -0,0 +1,53 @@ +// +// NRExploreNovelMenuDataSource.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import JXSegmentedView + +class NRExploreNovelMenuDataSource: JXSegmentedTitleDataSource { + + nonisolated override init() { + super.init() + } + + + + nonisolated override func registerCellClass(in segmentedView: JXSegmentedView) { + MainActor.assumeIsolated { + segmentedView.collectionView.register(MenuCell.self, forCellWithReuseIdentifier: "MenuCell") + } + } + + nonisolated override func segmentedView(_ segmentedView: JXSegmentedView, cellForItemAt index: Int) -> JXSegmentedBaseCell { + return MainActor.assumeIsolated { + let cell = segmentedView.dequeueReusableCell(withReuseIdentifier: "MenuCell", at: index) + return cell + } + } + +} + + + +extension NRExploreNovelMenuDataSource { + + class MenuCell: JXSegmentedTitleCell { + + override func commonInit() { + super.commonInit() + self.contentView.backgroundColor = .black.withAlphaComponent(0.05) + self.contentView.layer.cornerRadius = 12 + self.contentView.layer.masksToBounds = true + } + + override func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) { + super.reloadData(itemModel: itemModel, selectedType: selectedType) + } + + } + +} diff --git a/ReaderHive/Class/Explore/VM/NRExploreNovelViewModel.swift b/ReaderHive/Class/Explore/VM/NRExploreNovelViewModel.swift new file mode 100644 index 0000000..18179de --- /dev/null +++ b/ReaderHive/Class/Explore/VM/NRExploreNovelViewModel.swift @@ -0,0 +1,24 @@ +// +// NRExploreNovelViewModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit + +class NRExploreNovelViewModel: NSObject { + + lazy var menuDataArr: [NRExploreNovelMenuItem] = { + let arr = [ + NRExploreNovelMenuItem(type: .genres, title: "Genres".localized, icon: UIImage(named: "explore_genres_icon_01"), selectedIcon: UIImage(named: "explore_genres_icon_01_selected")), + NRExploreNovelMenuItem(type: .trending, title: "Trending".localized, icon: UIImage(named: "explore_trending_icon_01"), selectedIcon: UIImage(named: "explore_trending_icon_01_selected")), + NRExploreNovelMenuItem(type: .topRated, title: "Top Rated".localized, icon: UIImage(named: "explore_top_rated_icon_01"), selectedIcon: UIImage(named: "explore_top_rated_icon_01_selected")), + NRExploreNovelMenuItem(type: .collected, title: "Most Collected".localized, icon: UIImage(named: "explore_most_collected_icon_01"), selectedIcon: UIImage(named: "explore_most_collected_icon_01_selected")), + NRExploreNovelMenuItem(type: .bestSellers, title: "Best Sellers".localized, icon: UIImage(named: "explore_best_sellers_icon_01"), selectedIcon: UIImage(named: "explore_best_sellers_icon_01_selected")), + ] + return arr + }() + + +} diff --git a/ReaderHive/Class/Home/C/NRHomeNovelListViewController.swift b/ReaderHive/Class/Home/C/NRHomeNovelListViewController.swift new file mode 100644 index 0000000..fda7843 --- /dev/null +++ b/ReaderHive/Class/Home/C/NRHomeNovelListViewController.swift @@ -0,0 +1,188 @@ +// +// NRHomeNovelListViewController.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import SnapKit + + +class NRHomeNovelListViewController: NRViewController { + + lazy var dataArr: [NRNovelModel] = [] + lazy var page = 1 + + lazy var collectionViewLayout: NRWaterfallFlowLayout = { + let layout = NRWaterfallFlowLayout() + layout.delegate = self + return layout + }() + + lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.contentInset = .init(top: 0, left: 0, bottom:10, right: 0) + collectionView.nr_addRefreshFooter { [weak self] in + self?.handleFooterRefresh(nil) + } + collectionView.register(NRHomeNovelListCell.self, forCellWithReuseIdentifier: "cell") + collectionView.register(NRHomeNovelListTextCell.self, forCellWithReuseIdentifier: "textCell") + return collectionView + }() + + lazy var listTitleView: NRHomeNovelHeaderContentView = { + let view = NRHomeNovelHeaderContentView() + view.titleLabel.text = "More Stories".localized + return view + }() + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = .clear + self.backgroundImageView.isHidden = true + + NotificationCenter.default.addObserver(self, selector: #selector(networkStatusDidChangeNotification), name: NRNetworkReachableManager.networkStatusDidChangeNotification, object: nil) + + nr_setupUI() + + Task { + await requestDataArr(page: 1) + } + } + + + override func listScrollView() -> UIScrollView { + return collectionView + } + + override func handleHeaderRefresh(_ completer: (() -> Void)?) { + Task { + await requestDataArr(page: 1) + completer?() + } + } + + override func handleFooterRefresh(_ completer: (() -> Void)?) { + Task { + await requestDataArr(page: self.page + 1) + self.collectionView.nr_endFooterRefreshing() + completer?() + } + } + + @objc private func networkStatusDidChangeNotification() { + if NRNetworkReachableManager.manager.isReachable == true, self.dataArr.isEmpty { + Task { + await requestDataArr(page: 1) + } + } + } + +} + +extension NRHomeNovelListViewController { + + private func nr_setupUI() { + view.addSubview(listTitleView) + view.addSubview(collectionView) + + + listTitleView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + } + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() +// make.top.equalTo(listTitleView.snp.bottom) + make.top.equalToSuperview().offset(36) + } + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRHomeNovelListViewController: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let model = dataArr[indexPath.row] + let cellType = self.getCellType(indexPath: indexPath) + if cellType == 1 { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "textCell", for: indexPath) as! NRHomeNovelListTextCell + cell.model = model + return cell + } else { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRHomeNovelListCell + cell.model = model + return cell + } + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.dataArr.count + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + self.didScrollCallback?(scrollView) + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let vc = NRNovelDetailViewController() + vc.novelId = self.dataArr[indexPath.row].id ?? "" + self.navigationController?.pushViewController(vc, animated: true) + } + + func getCellType(indexPath: IndexPath) -> Int { + let num = indexPath.row + 1 + if num % 3 == 2 { + return 1 + } + return 0 + } +} + +//MARK: NRWaterfallMutiSectionDelegate +extension NRHomeNovelListViewController: NRWaterfallMutiSectionDelegate { + func heightForRowAtIndexPath(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, indexPath: IndexPath, itemWidth: CGFloat) -> CGFloat { + let cellType = self.getCellType(indexPath: indexPath) + if cellType == 1 { + return NRHomeNovelListTextCell.contentHeight + } else { + let coverHeight = NRHomeNovelListCell.coverHeight + return coverHeight + 86 + } + } + + func lineSpacing(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, section: Int) -> CGFloat { + return 16 + } + + func interitemSpacing(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, section: Int) -> CGFloat { + return 13 + } + + func insetForSection(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, section: Int) -> UIEdgeInsets { + return .init(top: 0, left: 15, bottom: 0, right: 15) + } +} + +extension NRHomeNovelListViewController { + + private func requestDataArr(page: Int) async { + guard let list = await NRHomeAPI.requestHomeNewData(page: page) else { return } + + if page == 1 { + self.dataArr.removeAll() + } + self.dataArr += list + self.page = page + self.collectionView.reloadData() + } + +} diff --git a/ReaderHive/Class/Home/C/NRHomeNovelNewViewController.swift b/ReaderHive/Class/Home/C/NRHomeNovelNewViewController.swift new file mode 100644 index 0000000..eb152c4 --- /dev/null +++ b/ReaderHive/Class/Home/C/NRHomeNovelNewViewController.swift @@ -0,0 +1,142 @@ +// +// NRHomeNovelNewViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import SnapKit +import LYEmptyView + +class NRHomeNovelNewViewController: NRViewController { + + + private lazy var dataArr: [NRNovelModel] = [] + private lazy var page = 1 + + lazy var collectionViewLayout: UICollectionViewCompositionalLayout = { + let itemWidth = (UIScreen.width - 32 - 40) / 3 + let itemHeight = 150 / 100 * itemWidth + 68 + + let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1 / 3), heightDimension: .fractionalHeight(1))) + + let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(itemHeight)), subitems: [item]) + group.interItemSpacing = .fixed(20) + + let section = NSCollectionLayoutSection(group: group) + section.interGroupSpacing = 18 + section.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16) + + let configuration = UICollectionViewCompositionalLayoutConfiguration() + + let layout = UICollectionViewCompositionalLayout(section: section, configuration: configuration) + + return layout + }() + + lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.ly_emptyView = NREmpty.nr_emptyView() + collectionView.contentInset = .init(top: 10, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) + collectionView.nr_addRefreshHeader(insetTop: collectionView.contentInset.top) { [weak self] in + self?.handleHeaderRefresh(nil) + } +// collectionView.nr_addRefreshFooter(insetBottom: collectionView.contentInset.bottom) { [weak self] in +// self?.handleFooterRefresh(nil) +// } + collectionView.register(NRNovelGenresCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.edgesForExtendedLayout = .top + self.backgroundImageView.isHidden = true + self.title = "New".localized + + configNavigationBack("arrow_left_icon_05") + + nr_setupUI() + + Task { + await requestDataArr(page: 1) + } + } + + + 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)?) { + Task { + await requestDataArr(page: 1) + self.collectionView.nr_endHeaderRefreshing() + } + } + + override func handleFooterRefresh(_ completer: (() -> Void)?) { + Task { + await requestDataArr(page: self.page + 1) + self.collectionView.nr_endFooterRefreshing() + } + } + +} + +extension NRHomeNovelNewViewController { + + private func nr_setupUI() { + view.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + } + + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRHomeNovelNewViewController: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRNovelGenresCell + cell.model = self.dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let model = dataArr[indexPath.row] + let vc = NRNovelDetailViewController() + vc.novelId = model.id ?? "" + self.navigationController?.pushViewController(vc, animated: true) + } + +} + +extension NRHomeNovelNewViewController { + + private func requestDataArr(page: Int) async { + guard let list = await NRHomeAPI.requestNewData() else { return } + + if page == 1 { + self.dataArr.removeAll() + } + self.dataArr += list + self.page = page + + self.collectionView.reloadData() + } + +} diff --git a/ReaderHive/Class/Home/C/NRHomeNovelViewController.swift b/ReaderHive/Class/Home/C/NRHomeNovelViewController.swift new file mode 100644 index 0000000..1b94897 --- /dev/null +++ b/ReaderHive/Class/Home/C/NRHomeNovelViewController.swift @@ -0,0 +1,168 @@ +// +// NRHomeNovelViewController.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import JXPagingView +import SnapKit + +class NRHomeNovelViewController: NRViewController { + + let viewModel = NRHomeNovelViewModel() + + lazy var bgView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.layer.cornerRadius = 16 + view.layer.masksToBounds = true + view.layer.borderWidth = 1 + view.layer.borderColor = UIColor.F_2_EFEE.cgColor + return view + }() + + lazy var headerView: NRHomeNovelHeaderView = { + let view = NRHomeNovelHeaderView() + view.heightDidChange = { [weak self] in + guard let self = self else { return } + self.pagerView.resizeTableHeaderViewHeight(animatable: false) + } + return view + }() + + lazy var pagerView: JXPagingView = { + let view = JXPagingView(delegate: self) + view.layer.masksToBounds = true + view.mainTableView.backgroundColor = .clear + view.listContainerView.listCellBackgroundColor = .clear + view.mainTableView.gestureDelegate = self + view.mainTableView.nr_addRefreshHeader { [weak self] in + self?.handleHeaderRefresh(nil) + } + return view + }() + + lazy var listVC: NRHomeNovelListViewController = { + let vc = NRHomeNovelListViewController() + return vc + }() + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = .clear + self.backgroundImageView.isHidden = true + + NotificationCenter.default.addObserver(self, selector: #selector(networkStatusDidChangeNotification), name: NRNetworkReachableManager.networkStatusDidChangeNotification, object: nil) + + Task { + await requestHomeData() + } + + + nr_setupUI() + } + + + override func handleHeaderRefresh(_ completer: (() -> Void)?) { + self.listVC.handleHeaderRefresh(nil) + Task { + await requestHomeData() + self.pagerView.mainTableView.nr_endHeaderRefreshing() + } + } + + @objc private func networkStatusDidChangeNotification() { + if NRNetworkReachableManager.manager.isReachable == true, self.viewModel.headerDataArr.isEmpty { + Task { + await requestHomeData() + } + } + } + +} + +extension NRHomeNovelViewController { + + private func nr_setupUI() { + + view.addSubview(bgView) + view.addSubview(pagerView) + + bgView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(-1) + make.right.equalToSuperview().offset(1) + make.top.equalToSuperview().offset(10) + make.bottom.equalToSuperview().offset(UIScreen.tabBarHeight) + } + + pagerView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(26) + } + } + +} + +//MARK: JXPagingViewDelegate +extension NRHomeNovelViewController: JXPagingViewDelegate { + func tableHeaderViewHeight(in pagingView: JXPagingView) -> Int { + return Int(ceil(self.headerView.contentHeight)) + } + + func tableHeaderView(in pagingView: JXPagingView) -> UIView { + return headerView + } + + func heightForPinSectionHeader(in pagingView: JXPagingView) -> Int { + return 0 + } + + func viewForPinSectionHeader(in pagingView: JXPagingView) -> UIView { + return UIView() + } + + func numberOfLists(in pagingView: JXPagingView) -> Int { + return 1 + } + + func pagingView(_ pagingView: JXPagingView, initListAtIndex index: Int) -> any JXPagingViewListViewDelegate { + return listVC + } +} + +//MARK: JXPagingMainTableViewGestureDelegate +extension NRHomeNovelViewController: JXPagingMainTableViewGestureDelegate { + func mainTableViewGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { +// if otherGestureRecognizer == menuView.collectionView.panGestureRecognizer { +// return false +// } + + if let view = otherGestureRecognizer.view { + var superview: UIView? = view.superview + while superview != nil { + if superview?.isKind(of: NRHomeNovelHeaderView.self) == true { + return false + } + superview = superview?.superview + } + } + + return gestureRecognizer.isKind(of: UIPanGestureRecognizer.self) && otherGestureRecognizer.isKind(of: UIPanGestureRecognizer.self) + } + +} + +extension NRHomeNovelViewController { + + private func requestHomeData() async { + await self.viewModel.requestHomeData() + self.headerView.dataArr = self.viewModel.headerDataArr + } + +} diff --git a/ReaderHive/Class/Home/C/NRHomeViewController.swift b/ReaderHive/Class/Home/C/NRHomeViewController.swift new file mode 100644 index 0000000..60747d1 --- /dev/null +++ b/ReaderHive/Class/Home/C/NRHomeViewController.swift @@ -0,0 +1,68 @@ +// +// NRHomeViewController.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import SnapKit + +class NRHomeViewController: NRViewController { + + + lazy var searchButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "search_icon_01"), for: .normal) + button.addAction(UIAction(handler: { [weak self] _ in + guard let self = self else { return } + let vc = NRSearchViewController() + self.navigationController?.pushViewController(vc, animated: true) + }), for: .touchUpInside) + return button + }() + + lazy var titleView = UIImageView(image: UIImage(named: "home_title_image")) + + lazy var novelVC = NRHomeNovelViewController() + + override func viewDidLoad() { + super.viewDidLoad() + + nr_setupUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + + +} + +extension NRHomeViewController { + + private func nr_setupUI() { + view.addSubview(searchButton) + view.addSubview(titleView) + addChild(novelVC) + view.addSubview(novelVC.view) + + searchButton.snp.makeConstraints { make in + make.height.equalTo(44) + make.right.equalToSuperview().offset(-16) + make.top.equalTo(UIScreen.safeTop) + } + + titleView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.centerY.equalTo(searchButton) + } + + novelVC.view.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + } + } + +} diff --git a/ReaderHive/Class/Home/C/NRSearchViewController.swift b/ReaderHive/Class/Home/C/NRSearchViewController.swift new file mode 100644 index 0000000..d5343a2 --- /dev/null +++ b/ReaderHive/Class/Home/C/NRSearchViewController.swift @@ -0,0 +1,109 @@ +// +// NRSearchViewController.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit +import SnapKit + +class NRSearchViewController: NRViewController { + + lazy var viewModel = NRSearchViewModel() + + private lazy var returnButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "arrow_left_icon_02"), for: .normal) + button.addAction(UIAction(handler: { [weak self] _ in + self?.nr_handleNavigationBack() + }), for: .touchUpInside) + return button + }() + + private lazy var textView: NRSearchInputView = { + let view = NRSearchInputView() + view.didSearch = { [weak self] text in + guard let self = self else { return } + self.search(text) + } + return view + }() + + private lazy var homeView: NRSearchHomeView = { + let view = NRSearchHomeView() + view.viewModel = viewModel + view.didSearch = { [weak self] text in + self?.textView.text = text + self?.search(text) + } + return view + }() + + private lazy var resultView: NRSearchResultView = { + let view = NRSearchResultView() + view.isHidden = true + view.viewModel = viewModel + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.backgroundImageView.isHidden = true + textView.becomeFirstResponder() + + + nr_setupUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + + private func search(_ text: String) { + if text.isEmpty { + homeView.isHidden = false + resultView.isHidden = true + } else { + homeView.isHidden = true + resultView.isHidden = false + } + + resultView.search(text) + self.viewModel.addSearchRecord(text: text) + } + +} + +extension NRSearchViewController { + + private func nr_setupUI() { + view.addSubview(returnButton) + view.addSubview(textView) + view.addSubview(homeView) + view.addSubview(resultView) + + returnButton.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview().offset(UIScreen.safeTop) + make.height.equalTo(44) + } + + textView.snp.makeConstraints { make in + make.centerY.equalTo(returnButton) + make.left.equalTo(returnButton.snp.right).offset(12) + make.right.equalToSuperview().offset(-16) + } + + homeView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(returnButton.snp.bottom) + } + + resultView.snp.makeConstraints { make in + make.edges.equalTo(homeView) + } + } + +} diff --git a/ReaderHive/Class/Home/M/NRHomeNovelModuleItem.swift b/ReaderHive/Class/Home/M/NRHomeNovelModuleItem.swift new file mode 100644 index 0000000..ab7e62f --- /dev/null +++ b/ReaderHive/Class/Home/M/NRHomeNovelModuleItem.swift @@ -0,0 +1,76 @@ +// +// NRHomeNovelModuleItem.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/2. +// + +import UIKit +import SmartCodable + +class NRHomeNovelModuleItem: NSObject, SmartCodable { + + enum ModuleKey: String, SmartCaseDefaultable { + case banner = "home_banner" + case v3_recommand = "home_v3_recommand" + ///分类推荐 + case cagetory_recommand = "home_cagetory_recommand" + case week_ranking = "week_ranking" + ///跑马灯 + case marquee = "marquee" + + case new_recommand = "new_recommand" + + case week_highest_recommend = "week_highest_recommend" + + case get_details_recommand = "get_details_recommand" + + case highest_payment_hot_video = "highest_payment_hot_video" + + case category_navigation = "category_navigation" + } + + required override init() { } + + + var module_key: ModuleKey? + var title: String? + var list: [NRNovelModel]? + + var categoryList: [NRCategoryModel]? + + @SmartAny + var data: Any? + + @IgnoredKey + var iconImage: UIImage? + + @IgnoredKey + var br_cellHeight: CGFloat? + + + + func didFinishMapping() { + if module_key == .category_navigation, let data = data as? [[String : Any]] { + self.categoryList = [NRCategoryModel].deserialize(from: data) + + } else if let data = data as? [[String : Any]] { + self.list = [NRNovelModel].deserialize(from: data) + + } else if let data = data as? [String : Any] { + var dataList: [[String : Any]]? + if let list = data["list"] as? [[String : Any]] { + self.title = data["title"] as? String + dataList = list + + } else if let list = data["shortPlayList"] as? [[String : Any]] { + self.title = data["category_name"] as? String + dataList = list + } + if let dataList = dataList { + self.list = [NRNovelModel].deserialize(from: dataList) + } + + } + } +} diff --git a/ReaderHive/Class/Home/M/NRReadWhatViewTransformer.swift b/ReaderHive/Class/Home/M/NRReadWhatViewTransformer.swift new file mode 100644 index 0000000..e93d7c0 --- /dev/null +++ b/ReaderHive/Class/Home/M/NRReadWhatViewTransformer.swift @@ -0,0 +1,20 @@ +// +// NRReadWhatViewTransformer.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit +import FSPagerView + +class NRReadWhatViewTransformer: FSPagerViewTransformer { + + nonisolated override init(type: FSPagerViewTransformerType) { + super.init(type: type) + } + + nonisolated override func proposedInteritemSpacing() -> CGFloat { + return -1 + } +} diff --git a/ReaderHive/Class/Home/V/NRHomeCategoryTagView.swift b/ReaderHive/Class/Home/V/NRHomeCategoryTagView.swift new file mode 100644 index 0000000..9338dd1 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeCategoryTagView.swift @@ -0,0 +1,53 @@ +// +// NRHomeCategoryTagView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit +import SnapKit + +class NRHomeCategoryTagView: UIView { + + + override var intrinsicContentSize: CGSize { + return .init(width: 10, height: 20) + } + + var text: String? { + set { + categoryLabel.text = newValue + } + get { + return categoryLabel.text + } + } + + lazy var categoryLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 10, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + backgroundColor = .black.withAlphaComponent(0.05) + layer.cornerRadius = 10 + layer.masksToBounds = true + + addSubview(categoryLabel) + + categoryLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + make.left.equalToSuperview().offset(8) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelHeaderContentView.swift b/ReaderHive/Class/Home/V/NRHomeNovelHeaderContentView.swift new file mode 100644 index 0000000..b389c3c --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelHeaderContentView.swift @@ -0,0 +1,69 @@ +// +// NRHomeNovelHeaderContentView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit +import SnapKit + +class NRHomeNovelHeaderContentView: UIView { + + lazy var titleView: UIView = { + let view = UIView() + return view + }() + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .semibold) + label.textColor = .black + return label + }() + + lazy var indicatorImageView = UIImageView(image: UIImage(named: "arrow_right_icon_01")) + + lazy var contentView: UIView = { + let view = UIView() + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + indicatorImageView.isHidden = true + + addSubview(titleView) + titleView.addSubview(titleLabel) + titleView.addSubview(indicatorImageView) + addSubview(contentView) + + titleView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.right.equalToSuperview().offset(-16) + make.top.equalToSuperview() + make.height.equalTo(24) + } + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview() + make.centerY.equalToSuperview() + } + + indicatorImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview() + } + + contentView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(titleView.snp.bottom).offset(12) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelHeaderView.swift b/ReaderHive/Class/Home/V/NRHomeNovelHeaderView.swift new file mode 100644 index 0000000..a6a965e --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelHeaderView.swift @@ -0,0 +1,172 @@ +// +// NRHomeNovelHeaderView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import SnapKit +import YYCategories + +class NRHomeNovelHeaderView: UIView { + + var contentHeight: CGFloat { + return scrollView.contentSize.height + 1 + } + + var heightDidChange: (() -> Void)? + + + var dataArr: [NRHomeNovelModuleItem]? { + didSet { + self.stackView.nr_removeAllArrangedSubview() + + dataArr?.forEach { item in + if item.module_key == .banner { + mustReadTodayView.dataArr = item.list ?? [] + stackView.addArrangedSubview(mustReadTodayView) + + } else if item.module_key == .new_recommand { + newArrivalsView.dataArr = item.list ?? [] + stackView.addArrangedSubview(newArrivalsView) + + } else if item.module_key == .get_details_recommand { + readWhatView.dataArr = item.list ?? [] + stackView.addArrangedSubview(readWhatView) + + } else if item.module_key == .highest_payment_hot_video { + hotGridView.dataArr = item.list ?? [] + stackView.addArrangedSubview(hotGridView) + + } else if item.module_key == .v3_recommand { + nextView.dataArr = item.list ?? [] + stackView.addArrangedSubview(nextView) + + } else if item.module_key == .category_navigation { + hotTagView.dataArr = item.categoryList ?? [] + stackView.addArrangedSubview(hotTagView) + + } else if item.module_key == .week_ranking { + featuredView.dataArr = item.list ?? [] + stackView.addArrangedSubview(featuredView) + + } + } + + + + } + } + + lazy var scrollView: NRScrollView = { + let scrollView = NRScrollView() + scrollView.isScrollEnabled = false + scrollView.addObserver(self, forKeyPath: "contentSize", context: nil) + return scrollView + }() + + lazy var stackView: UIStackView = { + let view = UIStackView() + view.axis = .vertical + view.spacing = 24 + return view + }() + + lazy var mustReadTodayView: NRHomeNovelMustReadTodayView = { + let view = NRHomeNovelMustReadTodayView() + return view + }() + + lazy var newArrivalsView: NRHomeNovelNewArrivalsView = { + let view = NRHomeNovelNewArrivalsView() + view.indicatorImageView.isHidden = false + + view.titleView.addGestureRecognizer(UITapGestureRecognizer(actionBlock: { [weak self] _ in + guard let self = self else { return } + let vc = NRHomeNovelNewViewController() + self.viewController?.navigationController?.pushViewController(vc, animated: true) + })) + return view + }() + + lazy var readWhatView: NRHomeNovelReadWhatView = { + let view = NRHomeNovelReadWhatView() + return view + }() + + lazy var hotGridView: NRHomeNovelHotGridView = { + let view = NRHomeNovelHotGridView() + return view + }() + + lazy var nextView: NRHomeNovelNextView = { + let view = NRHomeNovelNextView() + return view + }() + + lazy var hotTagView: NRHomeNovelHotTagView = { + let view = NRHomeNovelHotTagView() + return view + }() + + lazy var featuredView: NRHomeNovelHotGridView = { + let view = NRHomeNovelHotGridView() + view.titleLabel.text = "Featured".localized + return view + }() + +// lazy var listTitleView: NRHomeNovelHeaderContentView = { +// let view = NRHomeNovelHeaderContentView() +// view.titleLabel.text = "More Stories".localized +// return view +// }() + + deinit { + scrollView.removeObserver(self, forKeyPath: "contentSize", context: nil) + } + + override init(frame: CGRect) { + super.init(frame: frame) + + + + nr_setupUI() + } + + 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.heightDidChange?() + + let height = scrollView.contentSize.height + 1 + scrollView.snp.updateConstraints { make in + make.height.equalTo(height) + } + } + } +} + + +extension NRHomeNovelHeaderView { + + private func nr_setupUI() { + addSubview(scrollView) + scrollView.addSubview(stackView) + + scrollView.snp.makeConstraints { make in + make.left.right.top.bottom.equalToSuperview() + make.height.equalTo(1) + } + + stackView.snp.makeConstraints { make in + make.left.centerX.equalToSuperview() + make.top.equalToSuperview() + make.bottom.equalToSuperview().offset(-24) + } + } + +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelHotGridView.swift b/ReaderHive/Class/Home/V/NRHomeNovelHotGridView.swift new file mode 100644 index 0000000..0279bb8 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelHotGridView.swift @@ -0,0 +1,23 @@ +// +// NRHomeNovelHotGridView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit + +class NRHomeNovelHotGridView: NRHomeNovelNewArrivalsView { + + + + override init(frame: CGRect) { + super.init(frame: frame) + titleLabel.text = "Hot On The Grid".localized + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelHotTagCell.swift b/ReaderHive/Class/Home/V/NRHomeNovelHotTagCell.swift new file mode 100644 index 0000000..7a03a30 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelHotTagCell.swift @@ -0,0 +1,37 @@ +// +// NRHomeNovelHotTagCell.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit +import SnapKit + +class NRHomeNovelHotTagCell: UICollectionViewCell { + + lazy var textLabel: 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) + self.contentView.backgroundColor = .black.withAlphaComponent(0.05) + self.contentView.layer.cornerRadius = 12 + self.contentView.layer.masksToBounds = true + + + self.contentView.addSubview(textLabel) + + textLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelHotTagView.swift b/ReaderHive/Class/Home/V/NRHomeNovelHotTagView.swift new file mode 100644 index 0000000..9e1a128 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelHotTagView.swift @@ -0,0 +1,140 @@ +// +// NRHomeNovelHotTagView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit +import SnapKit +import collection_view_layouts + +class NRHomeNovelHotTagView: UIView { + + var dataArr: [NRCategoryModel] = [] { + didSet { + collectionView.reloadData() + } + } + + lazy var bgView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "bg_image_03")) + return imageView + }() + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .semibold) + label.textColor = .black + label.text = "Hot Tags".localized + return label + }() + + lazy var subtitleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .F_9710_D + return label + }() + + private lazy var collectionViewLayout: TagsLayout = { + let layout = TagsLayout() + layout.delegate = self + layout.contentPadding = ItemsPadding(horizontal: 16, vertical: 0) + layout.cellsPadding = ItemsPadding(horizontal: 12, vertical: 12) + return layout + }() + + private lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.addObserver(self, forKeyPath: "contentSize", context: nil) + collectionView.register(NRHomeNovelHotTagCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + deinit { + collectionView.removeObserver(self, forKeyPath: "contentSize") + } + + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = .F_6_F_6_F_6 + +// subtitleLabel.text = "128 books updated yesterday" + + nr_setupUI() + } + + 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" { + let height = self.collectionView.contentSize.height + 1 + + collectionView.snp.updateConstraints { make in + make.height.equalTo(height) + } + } + } + +} + +extension NRHomeNovelHotTagView { + + private func nr_setupUI() { + addSubview(bgView) + addSubview(titleLabel) +// addSubview(subtitleLabel) + addSubview(collectionView) + + + bgView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + } + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview().offset(12) + } + +// subtitleLabel.snp.makeConstraints { make in +// make.left.equalToSuperview().offset(16) +// make.top.equalTo(titleLabel.snp.bottom).offset(5) +// } + + collectionView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.top.equalTo(titleLabel.snp.bottom).offset(16) + make.bottom.equalToSuperview().offset(-16) + make.height.equalTo(1) + } + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRHomeNovelHotTagView: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRHomeNovelHotTagCell + cell.textLabel.text = dataArr[indexPath.row].name + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataArr.count + } + +} + +extension NRHomeNovelHotTagView: LayoutDelegate { + func cellSize(indexPath: IndexPath) -> CGSize { + let text = dataArr[indexPath.row].name ?? "" + let size = text.size(.font(ofSize: 12, weight: .regular), .init(width: UIScreen.width, height: 24)) + return .init(width: size.width + 24, height: 24) + } +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelListCell.swift b/ReaderHive/Class/Home/V/NRHomeNovelListCell.swift new file mode 100644 index 0000000..be091f1 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelListCell.swift @@ -0,0 +1,106 @@ +// +// NRHomeNovelListCell.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit +import SnapKit + +class NRHomeNovelListCell: UICollectionViewCell { + + static let coverHeight: CGFloat = { + let width = (UIScreen.width - 32 - 13) / 2 + let coverHeight = 247 / 165 * width + return coverHeight + }() + + var model: NRNovelModel? { + didSet { + coverImageView.nr_setImage(model?.image_url) + titleLabel.text = model?.name + desLabel.text = model?.nr_description + + if let text = model?.category?.first, text.count > 0 { + self.categooryView.isHidden = false + self.categooryView.text = text + } else { + self.categooryView.isHidden = true + } + } + } + + lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.nr_setRoundedCorner(topLeft: 4, topRight: 4, bottomLeft: 0, bottomRight: 0) + return imageView + }() + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + return label + }() + + lazy var desLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 10, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + label.numberOfLines = 2 + return label + }() + + lazy var categooryView: NRHomeCategoryTagView = { + let view = NRHomeCategoryTagView() + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + titleLabel.text = "Dungeons and Drama" + desLabel.text = "Struggling artist Lila signs a contract to marry the cold CEO Adrian Vance to save her family. Their \"business-only\" deal is shattered by electric tension and Adrian's jealous ex. As their facade crumbles under pressure, can their reluctant love survive the final, crushing truth?" + categooryView.text = "Satisfying" + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRHomeNovelListCell { + + private func nr_setupUI() { + contentView.addSubview(coverImageView) + contentView.addSubview(titleLabel) + contentView.addSubview(desLabel) + contentView.addSubview(categooryView) + + coverImageView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.height.equalTo(Self.coverHeight) + } + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview() + make.top.equalTo(coverImageView.snp.bottom).offset(8) + make.right.lessThanOrEqualToSuperview() + } + + desLabel.snp.makeConstraints { make in + make.left.equalToSuperview() + make.top.equalTo(titleLabel.snp.bottom).offset(4) + make.right.lessThanOrEqualToSuperview() + } + + categooryView.snp.makeConstraints { make in + make.bottom.equalToSuperview() + make.left.equalToSuperview() + } + } + +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelListTextCell.swift b/ReaderHive/Class/Home/V/NRHomeNovelListTextCell.swift new file mode 100644 index 0000000..36970ab --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelListTextCell.swift @@ -0,0 +1,121 @@ +// +// NRHomeNovelListTextCell.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit +import SnapKit + +class NRHomeNovelListTextCell: UICollectionViewCell { + + static let contentHeight = 218.0 + + var model: NRNovelModel? { + didSet { + textLabel.text = model?.nr_description + coverImageView.nr_setImage(model?.image_url) + nameLabel.text = model?.name + + categoryLabel.text = model?.category?.first + } + } + + lazy var textLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 10, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + label.numberOfLines = 0 + return label + }() + + lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + imageView.layer.masksToBounds = true + return imageView + }() + + lazy var lineView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.15) + return view + }() + + lazy var nameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black + label.numberOfLines = 2 + return label + }() + + lazy var categoryLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 10, weight: .regular) + label.textColor = .black.withAlphaComponent(0.25) + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + contentView.backgroundColor = .F_2_EFEE + contentView.layer.cornerRadius = 8 + contentView.layer.masksToBounds = true + + textLabel.text = "The lamp in the embroidery room burned late into the night. Shen Qingci gripped the ruined piece of silk, the prick of the needle a familiar sting. She knew the sharpest weapon in the world was not a sword, but the human heart. Since marrying into the Marquis's estate, she felt trapped in a swamp. The seemingly docile concubine's sister, the always smiling-but-lethal mother-in-law—all were waiting for her downfall. She took a deep breath, pushing back the tears. From this day forward, she would not be a victim, but the one who wielded life and death within these walls." + nameLabel.text = "Fortunes Reborn-The 90s Reckoning" + categoryLabel.text = "Satisfying" + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension NRHomeNovelListTextCell { + + private func nr_setupUI() { + contentView.addSubview(textLabel) + contentView.addSubview(coverImageView) + contentView.addSubview(lineView) + contentView.addSubview(nameLabel) + contentView.addSubview(categoryLabel) + + coverImageView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(8) + make.bottom.equalToSuperview().offset(-8) + make.width.equalTo(40) + make.height.equalTo(60) + } + + lineView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(8) + make.centerX.equalToSuperview() + make.height.equalTo(1) + make.bottom.equalTo(coverImageView.snp.top).offset(-8) + } + + textLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(8) + make.right.lessThanOrEqualToSuperview().offset(-8) + make.top.equalToSuperview().offset(8) + make.bottom.lessThanOrEqualTo(lineView.snp.top).offset(-8) + } + + nameLabel.snp.makeConstraints { make in + make.left.equalTo(coverImageView.snp.right).offset(8) + make.right.lessThanOrEqualToSuperview().offset(-8) + make.top.equalTo(coverImageView).offset(0) + } + + categoryLabel.snp.makeConstraints { make in + make.left.equalTo(nameLabel) + make.bottom.equalTo(coverImageView).offset(-7) + } + } + +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelMustReadTodayCell.swift b/ReaderHive/Class/Home/V/NRHomeNovelMustReadTodayCell.swift new file mode 100644 index 0000000..3841377 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelMustReadTodayCell.swift @@ -0,0 +1,179 @@ +// +// NRHomeNovelMustReadTodayCell.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit +import SnapKit + +class NRHomeNovelMustReadTodayCell: UICollectionViewCell { + + var model: NRNovelModel? { + didSet { + coverImageView.nr_setImage(model?.image_url) + titleLabel.text = model?.name + desLabel.text = model?.nr_description + + starView.grade = model?.rate ?? 0 + starView.text = NSNumber(value: model?.rate ?? 0).toString(maximumFractionDigits: 1, minimumFractionDigits: 1) + + if let text = model?.category?.first, text.count > 0 { + categoryView.isHidden = false + categoryLabel.text = text + } else { + categoryView.isHidden = true + } + } + } + + lazy var bgView: UIImageView = { + let view = UIImageView(image: UIImage(named: "home_cell_bg_image_01")) + return view + }() + + lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + imageView.layer.masksToBounds = true + return imageView + }() + + lazy var categoryView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.05) + view.layer.cornerRadius = 10 + view.layer.masksToBounds = true + return view + }() + + lazy var categoryLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 10, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + return label + }() + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + return label + }() + + lazy var desLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 10, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + label.numberOfLines = 2 + return label + }() + + lazy var readNowBgView: UIView = { + let view = NRGradientView() + view.colors = [UIColor.F_3912_F.cgColor, UIColor.FF_4_A_4_A.cgColor, UIColor.FA_9_B_1_F.cgColor] + view.startPoint = .init(x: 0, y: 0.5) + view.endPoint = .init(x: 1, y: 0.5) + view.layer.cornerRadius = 8 + view.layer.masksToBounds = true + return view + }() + + lazy var readNowLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .white + label.text = "Read Now".localized + return label + }() + + lazy var starView: NRStarGradeView = { + let view = NRStarGradeView() + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.contentView.layer.cornerRadius = 12 + self.contentView.layer.masksToBounds = true + + categoryLabel.text = "Satisfying" + titleLabel.text = "A Strike to the Heart" + desLabel.text = "Haunted by fading memories, a man navigates a labyrinth of dreams and reality, uncovering truths that blur the line between past and present." + + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRHomeNovelMustReadTodayCell { + + private func nr_setupUI() { + contentView.addSubview(bgView) + contentView.addSubview(coverImageView) + contentView.addSubview(categoryView) + categoryView.addSubview(categoryLabel) + contentView.addSubview(titleLabel) + contentView.addSubview(starView) + contentView.addSubview(desLabel) + contentView.addSubview(readNowBgView) + readNowBgView.addSubview(readNowLabel) + + bgView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + coverImageView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(12) + make.centerY.equalToSuperview() + make.width.equalTo(100) + make.height.equalTo(150) + } + + categoryView.snp.makeConstraints { make in + make.left.equalTo(coverImageView.snp.right).offset(12) + make.top.equalTo(coverImageView) + make.height.equalTo(20) + } + + categoryLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + make.left.equalToSuperview().offset(8) + } + + titleLabel.snp.makeConstraints { make in + make.left.equalTo(categoryView) + make.top.equalTo(coverImageView).offset(24) + make.right.lessThanOrEqualToSuperview().offset(-12) + } + + starView.snp.makeConstraints { make in + make.left.equalTo(titleLabel) + make.top.equalTo(titleLabel.snp.bottom).offset(6) + } + + desLabel.snp.makeConstraints { make in + make.left.equalTo(titleLabel) + make.top.equalTo(titleLabel.snp.bottom).offset(33) + make.right.lessThanOrEqualToSuperview().offset(-12) + } + + readNowBgView.snp.makeConstraints { make in + make.left.equalTo(titleLabel) + make.bottom.equalTo(coverImageView) + make.right.equalToSuperview().offset(-12) + make.height.equalTo(36) + } + + readNowLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } + +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelMustReadTodayView.swift b/ReaderHive/Class/Home/V/NRHomeNovelMustReadTodayView.swift new file mode 100644 index 0000000..5bfa8c3 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelMustReadTodayView.swift @@ -0,0 +1,86 @@ +// +// NRHomeNovelMustReadTodayView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit +import SnapKit +import YYCategories + +class NRHomeNovelMustReadTodayView: NRHomeNovelHeaderContentView { + + + var dataArr: [NRNovelModel] = [] { + didSet { + self.collectionView.reloadData() + } + } + + + lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.itemSize = .init(width: 316, height: 174) + layout.minimumLineSpacing = 16 + return layout + }() + + lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.showsHorizontalScrollIndicator = false + collectionView.contentInset = .init(top: 0, left: 16, bottom: 0, right: 16) + collectionView.register(NRHomeNovelMustReadTodayCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.titleLabel.text = "Must-Read Today".localized + + nr_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRHomeNovelMustReadTodayView { + + private func nr_setupUI() { + contentView.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.bottom.equalToSuperview() + make.height.equalTo(self.collectionViewLayout.itemSize.height) + } + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRHomeNovelMustReadTodayView: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRHomeNovelMustReadTodayCell + cell.model = self.dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let vc = NRNovelDetailViewController() + vc.novelId = dataArr[indexPath.row].id ?? "" + self.viewController?.navigationController?.pushViewController(vc, animated: true) + } + +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelNewArrivalsCell.swift b/ReaderHive/Class/Home/V/NRHomeNovelNewArrivalsCell.swift new file mode 100644 index 0000000..4ed79a6 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelNewArrivalsCell.swift @@ -0,0 +1,128 @@ +// +// NRHomeNovelNewArrivalsCell.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit +import SnapKit + +class NRHomeNovelNewArrivalsCell: UICollectionViewCell { + + var model: NRNovelModel? { + didSet { + coverImageView.nr_setImage(model?.image_url) + titleLabel.text = model?.name + + if let text = model?.category?.first, text.count > 0 { + categoryView.isHidden = false + categoryLabel.text = text + } else if let text = model?.categoryList?.first?.name, text.count > 0 { + categoryView.isHidden = false + categoryLabel.text = text + } else { + categoryView.isHidden = true + } + + + + + if model?.tag_type == .new { + markImageView.image = UIImage(named: "new_icon_01") + markImageView.isHidden = false + } else if model?.tag_type == .hot { + markImageView.image = UIImage(named: "hot_icon_03") + markImageView.isHidden = false + } else { + markImageView.isHidden = true + } + } + } + + lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + imageView.layer.masksToBounds = true + return imageView + }() + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black + label.numberOfLines = 2 + return label + }() + + lazy var categoryView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.05) + view.layer.cornerRadius = 10 + view.layer.masksToBounds = true + return view + }() + + lazy var categoryLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 10, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + return label + }() + + lazy var markImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "new_icon_01")) + return imageView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRHomeNovelNewArrivalsCell { + + + private func nr_setupUI() { + contentView.addSubview(coverImageView) + coverImageView.addSubview(markImageView) + contentView.addSubview(titleLabel) + contentView.addSubview(categoryView) + categoryView.addSubview(categoryLabel) + + + coverImageView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.height.equalTo(135) + } + + markImageView.snp.makeConstraints { make in + make.left.top.equalToSuperview() + } + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview() + make.right.lessThanOrEqualToSuperview() + make.top.equalTo(coverImageView.snp.bottom).offset(4) + } + + categoryView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.top.equalTo(titleLabel.snp.bottom).offset(4) + make.height.equalTo(20) + } + + categoryLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + make.left.equalToSuperview().offset(8) + } + } + +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelNewArrivalsView.swift b/ReaderHive/Class/Home/V/NRHomeNovelNewArrivalsView.swift new file mode 100644 index 0000000..f930383 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelNewArrivalsView.swift @@ -0,0 +1,84 @@ +// +// NRHomeNovelNewArrivalsView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit +import SnapKit +import YYCategories + +class NRHomeNovelNewArrivalsView: NRHomeNovelHeaderContentView { + + var dataArr: [NRNovelModel] = [] { + didSet { + collectionView.reloadData() + } + } + + lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.itemSize = .init(width: 90, height: 195) + layout.minimumLineSpacing = 12 + return layout + }() + + lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.showsHorizontalScrollIndicator = false + collectionView.contentInset = .init(top: 0, left: 16, bottom: 0, right: 16) + collectionView.register(NRHomeNovelNewArrivalsCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.titleLabel.text = "New Arrivals".localized + + nr_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + + + +extension NRHomeNovelNewArrivalsView { + + private func nr_setupUI() { + contentView.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.left.right.top.bottom.equalToSuperview() + make.height.equalTo(self.collectionViewLayout.itemSize.height) + } + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRHomeNovelNewArrivalsView: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRHomeNovelNewArrivalsCell + cell.model = dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let vc = NRNovelDetailViewController() + vc.novelId = dataArr[indexPath.row].id ?? "" + self.viewController?.navigationController?.pushViewController(vc, animated: true) + } +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelNextView.swift b/ReaderHive/Class/Home/V/NRHomeNovelNextView.swift new file mode 100644 index 0000000..0853cbb --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelNextView.swift @@ -0,0 +1,89 @@ +// +// NRHomeNovelNextView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit +import SnapKit +import YYCategories + +class NRHomeNovelNextView: NRHomeNovelHeaderContentView { + + var dataArr: [NRNovelModel] = [] { + didSet { + collectionView.reloadData() + } + } + + lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let itemWidth = (UIScreen.width - 32 - 30) / 3 + let itemHeight = 150 / 100 * itemWidth + 68 + + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.itemSize = .init(width: itemWidth, height: itemHeight) + layout.minimumLineSpacing = 15 + layout.minimumInteritemSpacing = 18 + return layout + }() + + lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.isScrollEnabled = false + collectionView.delegate = self + collectionView.dataSource = self + collectionView.showsHorizontalScrollIndicator = false + collectionView.contentInset = .init(top: 0, left: 16, bottom: 0, right: 16) + collectionView.register(NRHomeNovelNextViewCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.titleLabel.text = "Next In View".localized + + nr_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRHomeNovelNextView { + + private func nr_setupUI() { + contentView.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.edges.equalToSuperview() + make.height.equalTo(collectionViewLayout.itemSize.height * 2 + collectionViewLayout.minimumInteritemSpacing) + } + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRHomeNovelNextView: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRHomeNovelNextViewCell + cell.model = self.dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return min(dataArr.count, 6) + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let vc = NRNovelDetailViewController() + vc.novelId = dataArr[indexPath.row].id ?? "" + self.viewController?.navigationController?.pushViewController(vc, animated: true) + } +} + + diff --git a/ReaderHive/Class/Home/V/NRHomeNovelNextViewCell.swift b/ReaderHive/Class/Home/V/NRHomeNovelNextViewCell.swift new file mode 100644 index 0000000..e590613 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelNextViewCell.swift @@ -0,0 +1,128 @@ +// +// NRHomeNovelNextViewCell.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit +import SnapKit + +class NRHomeNovelNextViewCell: UICollectionViewCell { + + var model: NRNovelModel? { + didSet { + coverImageView.nr_setImage(model?.image_url) + titleLabel.text = model?.name + + if let text = model?.category?.first, text.count > 0 { + categoryView.isHidden = false + categoryLabel.text = text + } else if let text = model?.categoryList?.first?.name, text.count > 0 { + categoryView.isHidden = false + categoryLabel.text = text + } else { + categoryView.isHidden = true + } + + if model?.tag_type == .new { + markImageView.image = UIImage(named: "new_icon_01") + markImageView.isHidden = false + } else if model?.tag_type == .hot { + markImageView.image = UIImage(named: "hot_icon_03") + markImageView.isHidden = false + } else { + markImageView.isHidden = true + } + } + } + + lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + imageView.layer.masksToBounds = true + return imageView + }() + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + label.numberOfLines = 2 + return label + }() + + lazy var categoryView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.05) + view.layer.cornerRadius = 10 + view.layer.masksToBounds = true + return view + }() + + lazy var categoryLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 10, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + return label + }() + + lazy var markImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "new_icon_01")) + return imageView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + categoryLabel.text = "Satisfying" + titleLabel.text = "Vanished Without a Word" + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + +extension NRHomeNovelNextViewCell { + + + private func nr_setupUI() { + contentView.addSubview(coverImageView) + coverImageView.addSubview(markImageView) + contentView.addSubview(titleLabel) + contentView.addSubview(categoryView) + categoryView.addSubview(categoryLabel) + + + coverImageView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.bottom.equalToSuperview().offset(-68) + } + + markImageView.snp.makeConstraints { make in + make.left.top.equalToSuperview() + } + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview() + make.right.lessThanOrEqualToSuperview() + make.top.equalTo(coverImageView.snp.bottom).offset(8) + } + + categoryView.snp.makeConstraints { make in + make.left.equalToSuperview() +// make.top.equalTo(titleLabel.snp.bottom).offset(4) + make.bottom.equalToSuperview() + make.height.equalTo(20) + } + + categoryLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + make.left.equalToSuperview().offset(8) + } + } + +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelReadWhatCell.swift b/ReaderHive/Class/Home/V/NRHomeNovelReadWhatCell.swift new file mode 100644 index 0000000..6d0712c --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelReadWhatCell.swift @@ -0,0 +1,50 @@ +// +// NRHomeNovelReadWhatCell.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit +import FSPagerView +import SnapKit + +class NRHomeNovelReadWhatCell: FSPagerViewCell { + + var model: NRNovelModel? { + didSet { + coverImageView.nr_setImage(model?.image_url) + } + } + + lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + imageView.layer.masksToBounds = true + return imageView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.contentView.layer.shadowColor = UIColor.clear.cgColor + nr_setupUI() + } + + @MainActor required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + + +extension NRHomeNovelReadWhatCell { + + private func nr_setupUI() { + contentView.addSubview(coverImageView) + + coverImageView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + +} diff --git a/ReaderHive/Class/Home/V/NRHomeNovelReadWhatView.swift b/ReaderHive/Class/Home/V/NRHomeNovelReadWhatView.swift new file mode 100644 index 0000000..6731088 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRHomeNovelReadWhatView.swift @@ -0,0 +1,301 @@ +// +// NRHomeNovelReadWhatView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit +import SnapKit +import FSPagerView +import YYCategories + +class NRHomeNovelReadWhatView: UIView { + + var dataArr: [NRNovelModel] = [] { + didSet { + pageView.reloadData() + + updateCurrentNodelData() + + + + } + } + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .semibold) + label.textColor = .black + label.text = "read_what_title".localized + return label + }() + + lazy var subtitleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .F_9710_D + label.text = "read_what_subtitle".localized + return label + }() + + lazy var bgView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "bg_image_02")) + imageView.isUserInteractionEnabled = true + return imageView + }() + + lazy var pageView: FSPagerView = { + let transformer = NRReadWhatViewTransformer(type: .linear) + transformer.minimumScale = 0.7 + + let view = FSPagerView() + view.layer.masksToBounds = false + view.transformer = transformer + view.delegate = self + view.dataSource = self + view.isInfinite = true + view.itemSize = .init(width: 120, height: 180) + view.register(NRHomeNovelReadWhatCell.self, forCellWithReuseIdentifier: "cell") + return view + }() + + lazy var nameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + return label + }() + + lazy var desLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + label.numberOfLines = 4 + return label + }() + + lazy var readNowButton: UIButton = { + var configuration = UIButton.Configuration.plain() + configuration.image = UIImage(named: "arrow_right_icon_02") + configuration.imagePlacement = .trailing + configuration.imagePadding = 4 + configuration.attributedTitle = AttributedString("Read Now".localized, attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 14, weight: .medium), + .foregroundColor : UIColor.black + ])) + configuration.contentInsets = .init(top: 0, leading: 13, bottom: 0, trailing: 13) + + let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + + let vc = NRNovelDetailViewController() + vc.novelId = self.dataArr[self.pageView.currentIndex].id ?? "" + self.viewController?.navigationController?.pushViewController(vc, animated: true) + + })) + button.layer.cornerRadius = 12 + button.layer.masksToBounds = true + button.layer.borderColor = UIColor.black.withAlphaComponent(0.25).cgColor + button.layer.borderWidth = 1 + return button + }() + + lazy var tagStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = 5 + return stackView + }() + + lazy var categoryView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.05) + view.layer.cornerRadius = 10 + view.layer.masksToBounds = true + return view + }() + + lazy var categoryLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 10, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + return label + }() + + lazy var hotView: UIButton = { + var configuration = UIButton.Configuration.plain() + configuration.image = UIImage(named: "hot_icon_01") + configuration.background.backgroundColor = .black.withAlphaComponent(0.05) + configuration.contentInsets = .init(top: 0, leading: 8, bottom: 0, trailing: 8) + configuration.imagePadding = 0 + + let button = UIButton(configuration: configuration) + button.layer.cornerRadius = 10 + button.layer.masksToBounds = true + button.isUserInteractionEnabled = false + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + let index = self.pageView.currentIndex + if index >= self.dataArr.count { return } + let model = self.dataArr[index] + + button.configuration?.attributedTitle = AttributedString(NSNumber(value: model.watch_total ?? 0).formattedNumber(), attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 10, weight: .regular), + .foregroundColor : UIColor.black.withAlphaComponent(0.5) + ])) + } + return button + }() + + lazy var starView: NRStarGradeView = { + let view = NRStarGradeView() + view.grade = 0 + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + +// tagStackView.addArrangedSubview(categoryView) +// tagStackView.addArrangedSubview(hotView) + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func updateCurrentNodelData() { + let index = self.pageView.currentIndex + nrPrint(message: index) + let model = dataArr[index] + + nameLabel.text = model.name + desLabel.text = model.nr_description + + starView.grade = model.rate ?? 0 + starView.text = NSNumber(value: model.rate ?? 0).toString(maximumFractionDigits: 1, minimumFractionDigits: 1) + + tagStackView.nr_removeAllArrangedSubview() + + if let text = model.category?.first, text.count > 0 { + categoryLabel.text = text + tagStackView.addArrangedSubview(categoryView) + } + tagStackView.addArrangedSubview(hotView) + + hotView.setNeedsUpdateConfiguration() + } + +} + +extension NRHomeNovelReadWhatView { + + private func nr_setupUI() { + addSubview(titleLabel) + addSubview(subtitleLabel) + addSubview(bgView) + bgView.addSubview(pageView) + bgView.addSubview(nameLabel) + bgView.addSubview(tagStackView) + bgView.addSubview(desLabel) + bgView.addSubview(readNowButton) + bgView.addSubview(starView) + categoryView.addSubview(categoryLabel) + + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview().offset(12) + } + + subtitleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalTo(titleLabel.snp.bottom).offset(5) + } + + bgView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + make.top.equalTo(subtitleLabel.snp.bottom).offset(16) + make.height.equalTo(385) + make.bottom.equalToSuperview() + } + + pageView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().offset(12) + make.height.equalTo(180) + } + + nameLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.right.lessThanOrEqualToSuperview().offset(-16) + make.top.equalTo(pageView.snp.bottom).offset(12) + } + + tagStackView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.height.equalTo(20) + make.top.equalTo(nameLabel.snp.bottom).offset(8) + } + + desLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.right.lessThanOrEqualToSuperview().offset(-16) + make.top.equalTo(nameLabel.snp.bottom).offset(36) + } + + readNowButton.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-16) + make.height.equalTo(24) +// make.top.equalTo(desLabel.snp.bottom).offset(20) + make.bottom.equalToSuperview().offset(-12) + } + + starView.snp.makeConstraints { make in + make.centerY.equalTo(readNowButton) + make.left.equalToSuperview().offset(16) +// make.width.equalTo(90) +// make.height.equalTo(16) + } + + categoryLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + make.left.equalToSuperview().offset(8) + } + } + +} + +//MARK: FSPagerViewDelegate FSPagerViewDataSource +extension NRHomeNovelReadWhatView: FSPagerViewDelegate, FSPagerViewDataSource { + + func pagerView(_ pagerView: FSPagerView, cellForItemAt index: Int) -> FSPagerViewCell { + let cell = pagerView.dequeueReusableCell(withReuseIdentifier: "cell", at: index) as! NRHomeNovelReadWhatCell + cell.model = dataArr[index] + return cell + } + + func numberOfItems(in pagerView: FSPagerView) -> Int { + return dataArr.count + } + + func pagerViewDidEndScrollAnimation(_ pagerView: FSPagerView) { + nrPrint(message: "pagerViewDidEndScrollAnimation") + } + + func pagerViewDidEndDecelerating(_ pagerView: FSPagerView) { + nrPrint(message: "pagerViewDidEndDecelerating") + updateCurrentNodelData() + } + + func pagerView(_ pagerView: FSPagerView, didSelectItemAt index: Int) { + let vc = NRNovelDetailViewController() + vc.novelId = dataArr[index].id ?? "" + self.viewController?.navigationController?.pushViewController(vc, animated: true) + } + +} diff --git a/ReaderHive/Class/Home/V/NRSearchHomeView.swift b/ReaderHive/Class/Home/V/NRSearchHomeView.swift new file mode 100644 index 0000000..67c2f4f --- /dev/null +++ b/ReaderHive/Class/Home/V/NRSearchHomeView.swift @@ -0,0 +1,88 @@ +// +// NRSearchHomeView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit +import SnapKit + +class NRSearchHomeView: UIView { + + weak var viewModel: NRSearchViewModel? { + didSet { +// viewModel?.addObserver(self, forKeyPath: "recommendData", context: nil) + viewModel?.addObserver(self, forKeyPath: "recordList", context: nil) + + self.recordView.dataArr = self.viewModel?.recordList ?? [] + updateLayout() + } + } + + var didSearch: ((_ text: String) -> Void)? + + private lazy var stackView: UIStackView = { + let view = UIStackView() + view.spacing = 20 + view.axis = .vertical + return view + }() + + private lazy var recordView: NRSearchRecordView = { + let view = NRSearchRecordView() + view.didSearch = { [weak self] text in + self?.didSearch?(text) + } + view.didDelete = { [weak self] in + self?.viewModel?.clearSearchRecord() + } + return view + }() + + + + deinit { + self.viewModel?.removeObserver(self, forKeyPath: "recordList") + } + + override init(frame: CGRect) { + super.init(frame: frame) + updateLayout() + + nr_setupUI() + } + + 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 == "recordList" { + self.recordView.dataArr = self.viewModel?.recordList ?? [] + } + updateLayout() + } + + func updateLayout() { + stackView.nr_removeAllArrangedSubview() + + if self.recordView.dataArr.count > 0 { + stackView.addArrangedSubview(recordView) + } + + } + +} + +extension NRSearchHomeView { + private func nr_setupUI() { + addSubview(stackView) + + stackView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().offset(26) + make.bottom.lessThanOrEqualToSuperview() + } + } +} diff --git a/ReaderHive/Class/Home/V/NRSearchInputView.swift b/ReaderHive/Class/Home/V/NRSearchInputView.swift new file mode 100644 index 0000000..8fe3fb5 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRSearchInputView.swift @@ -0,0 +1,109 @@ +// +// NRSearchInputView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit +import SnapKit + +class NRSearchInputView: UIView { + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: 36) + } + + + + var didSearch: ((_ text: String) -> Void)? + + var text: String? { + get { + return textField.text + } + set { + textField.text = newValue + } + } + + private lazy var iconImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "search_icon_02")) + imageView.setContentHuggingPriority(.required, for: .horizontal) + imageView.setContentCompressionResistancePriority(.required, for: .horizontal) + return imageView + }() + + private lazy var textField: UITextField = { + let textField = UITextField(frame: .zero) + textField.tintColor = UIColor.black.withAlphaComponent(0.5) + textField.delegate = self + textField.returnKeyType = .search + textField.font = .font(ofSize: 12, weight: .regular) + textField.textColor = .black.withAlphaComponent(0.5) + textField.attributedPlaceholder = NSAttributedString(string: "search_placeholder".localized, attributes: [ + .font : UIFont.font(ofSize: 12, weight: .regular), + .foregroundColor : UIColor.black.withAlphaComponent(0.15) + ]) + return textField + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.layer.cornerRadius = 18 + self.layer.masksToBounds = true + self.backgroundColor = .F_2_EFEE + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @discardableResult + override func becomeFirstResponder() -> Bool { + super.becomeFirstResponder() + return self.textField.becomeFirstResponder() + } + + @discardableResult + override func resignFirstResponder() -> Bool { + super.resignFirstResponder() + return self.textField.resignFirstResponder() + } + +} + +extension NRSearchInputView { + + private func nr_setupUI() { + addSubview(iconImageView) + addSubview(textField) + + iconImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(12) + } + + textField.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.left.equalTo(iconImageView.snp.right).offset(10) + make.right.equalToSuperview().offset(-15) + } + } + +} + +//MARK: UITextFieldDelegate +extension NRSearchInputView: UITextFieldDelegate { + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + if let text = textField.text { + self.didSearch?(text) + } + return true + } + +} diff --git a/ReaderHive/Class/Home/V/NRSearchRecordCell.swift b/ReaderHive/Class/Home/V/NRSearchRecordCell.swift new file mode 100644 index 0000000..e30bffa --- /dev/null +++ b/ReaderHive/Class/Home/V/NRSearchRecordCell.swift @@ -0,0 +1,40 @@ +// +// NRSearchRecordCell.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit +import SnapKit + +class NRSearchRecordCell: UICollectionViewCell { + + static let TextFont: UIFont = .font(ofSize: 10, weight: .regular) + + lazy var textLabel: UILabel = { + let label = UILabel() + label.font = Self.TextFont + label.textColor = .black.withAlphaComponent(0.5) + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + contentView.layer.cornerRadius = 10 + contentView.layer.masksToBounds = true + contentView.backgroundColor = .black.withAlphaComponent(0.05) + + + contentView.addSubview(textLabel) + + textLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/ReaderHive/Class/Home/V/NRSearchRecordView.swift b/ReaderHive/Class/Home/V/NRSearchRecordView.swift new file mode 100644 index 0000000..bedc8f9 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRSearchRecordView.swift @@ -0,0 +1,136 @@ +// +// NRSearchRecordView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit +import collection_view_layouts +import SnapKit +import YYCategories + +class NRSearchRecordView: UIView { + + var didSearch: ((_ text: String) -> Void)? + var didDelete: (() -> Void)? + + var dataArr: [String] = [] { + didSet { + self.collectionView.reloadData() + } + } + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .semibold) + label.textColor = .black + label.text = "Recent Searches".localized + return label + }() + + private lazy var deleteButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "delete_icon_01"), for: .normal) + button.addAction(UIAction(handler: { [weak self] _ in + self?.didDelete?() + }), for: .touchUpInside) + return button + }() + + private lazy var collectionViewLayout: TagsLayout = { + let layout = TagsLayout() + layout.delegate = self + layout.contentPadding = ItemsPadding(horizontal: 16, vertical: 0) + layout.cellsPadding = ItemsPadding(horizontal: 12, vertical: 12) + return layout + }() + + private lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.addObserver(self, forKeyPath: "contentSize", context: nil) + collectionView.register(NRSearchRecordCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + deinit { + self.collectionView.removeObserver(self, forKeyPath: "contentSize") + } + + override init(frame: CGRect) { + super.init(frame: frame) + + fa_setupLayout() + } + + 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" { + let height = self.collectionView.contentSize.height + self.collectionView.snp.updateConstraints { make in + make.height.equalTo(height + 1) + } + } + } +} + +extension NRSearchRecordView { + + private func fa_setupLayout() { + addSubview(titleLabel) + addSubview(deleteButton) + addSubview(collectionView) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerY.equalTo(deleteButton) + } + + deleteButton.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-16) + make.top.equalToSuperview() + } + + collectionView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(36) + make.left.equalToSuperview() + make.right.equalToSuperview() + make.bottom.equalToSuperview() + make.height.equalTo(1) + } + } + +} + +//MARK: UICollectionViewDataSource UICollectionViewDataSource +extension NRSearchRecordView: UICollectionViewDataSource, UICollectionViewDelegate { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRSearchRecordCell + cell.textLabel.text = dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + self.didSearch?(dataArr[indexPath.row]) + } +} + +//MARK: LayoutDelegate +extension NRSearchRecordView: LayoutDelegate { + + func cellSize(indexPath: IndexPath) -> CGSize { + let text = dataArr[indexPath.row] + let size = text.size(NRSearchRecordCell.TextFont, .init(width: UIScreen.width, height: 24)) + return .init(width: size.width + 16, height: 20) + } +} diff --git a/ReaderHive/Class/Home/V/NRSearchResultCell.swift b/ReaderHive/Class/Home/V/NRSearchResultCell.swift new file mode 100644 index 0000000..b23fc34 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRSearchResultCell.swift @@ -0,0 +1,137 @@ +// +// NRSearchResultCell.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit +import SnapKit + +class NRSearchResultCell: UICollectionViewCell { + + var model: NRNovelModel? { + didSet { + coverImageView.nr_setImage(model?.image_url) + desLabel.text = model?.name + + tagStackView.nr_removeAllArrangedSubview() + + if let text = model?.categoryList?.first?.name, text.count > 0 { + categoryView.text = text + tagStackView.addArrangedSubview(categoryView) + } + tagStackView.addArrangedSubview(hotView) + hotView.setNeedsUpdateConfiguration() + } + } + + lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + return imageView + }() + + + lazy var tagStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = 8 + return stackView + }() + + lazy var categoryView: NRHomeCategoryTagView = { + let view = NRHomeCategoryTagView() + return view + }() + + lazy var hotView: UIButton = { + var configuration = UIButton.Configuration.plain() + configuration.image = UIImage(named: "hot_icon_01") + configuration.background.backgroundColor = .black.withAlphaComponent(0.05) + configuration.contentInsets = .init(top: 0, leading: 8, bottom: 0, trailing: 8) + configuration.imagePadding = 0 + + let button = UIButton(configuration: configuration) + button.layer.cornerRadius = 10 + button.layer.masksToBounds = true + button.isUserInteractionEnabled = false + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + let text = NSNumber(value: model?.watch_total ?? 0).formattedNumber() + + button.configuration?.attributedTitle = AttributedString(text, attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 10, weight: .regular), + .foregroundColor : UIColor.black.withAlphaComponent(0.5) + ])) + } + return button + }() + + lazy var nameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + return label + }() + + lazy var desLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 10, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + label.numberOfLines = 2 + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + categoryView.text = "Satisfying" + nameLabel.text = "My Dark Romeo" + desLabel.text = "Haunted by fading memories, a man navigates a labyrinth of dreams and reality, uncovering truths that blur the line between past and present." + + tagStackView.addArrangedSubview(categoryView) + tagStackView.addArrangedSubview(hotView) +// categoryView.addSubview(hotView) + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension NRSearchResultCell { + + private func nr_setupUI() { + contentView.addSubview(coverImageView) + contentView.addSubview(tagStackView) + contentView.addSubview(nameLabel) + contentView.addSubview(desLabel) + + coverImageView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.bottom.equalToSuperview() + make.width.equalTo(60) + } + + tagStackView.snp.makeConstraints { make in + make.left.equalTo(coverImageView.snp.right).offset(12) + make.bottom.equalToSuperview() + make.height.equalTo(20) + } + + nameLabel.snp.makeConstraints { make in + make.left.equalTo(coverImageView.snp.right).offset(12) + make.right.lessThanOrEqualToSuperview().offset(-16) + make.top.equalToSuperview() + } + + desLabel.snp.makeConstraints { make in + make.left.equalTo(nameLabel) + make.right.lessThanOrEqualToSuperview().offset(-16) + make.top.equalTo(nameLabel.snp.bottom).offset(7) + } + } + +} diff --git a/ReaderHive/Class/Home/V/NRSearchResultView.swift b/ReaderHive/Class/Home/V/NRSearchResultView.swift new file mode 100644 index 0000000..3094d16 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRSearchResultView.swift @@ -0,0 +1,106 @@ +// +// NRSearchResultView.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit +import SnapKit +import LYEmptyView +import YYCategories + +class NRSearchResultView: UIView { + + weak var viewModel: NRSearchViewModel? + + var searchText: String = "" + + lazy var dataArr: [NRNovelModel] = [] + + lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.itemSize = .init(width: UIScreen.width, height: 90) + layout.minimumLineSpacing = 16 + layout.minimumInteritemSpacing = 16 + return layout + }() + + lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.contentInset = .init(top: 10, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) + collectionView.ly_emptyView = NREmpty.nr_emptyView() + collectionView.register(NRSearchResultCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func search(_ text: String) { + if text.isEmpty { + self.dataArr.removeAll() + self.collectionView.reloadData() + return + } + self.searchText = text + + Task { + await requestDataArr(text: self.searchText) + } + } +} + +extension NRSearchResultView { + + private func nr_setupUI() { + addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.left.bottom.right.equalToSuperview() + make.top.equalToSuperview().offset(10) + } + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRSearchResultView: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRSearchResultCell + cell.model = self.dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let model = self.dataArr[indexPath.row] + let vc = NRNovelDetailViewController() + vc.novelId = model.id ?? "" + self.viewController?.navigationController?.pushViewController(vc, animated: true) + } +} + +extension NRSearchResultView { + + private func requestDataArr(text: String) async { + guard let list = await NRHomeAPI.requestSearchNovel(text: text) else { return } + guard text == self.searchText else { return } + + self.dataArr = list + self.collectionView.reloadData() + } + +} diff --git a/ReaderHive/Class/Home/V/NRStarGradeView.swift b/ReaderHive/Class/Home/V/NRStarGradeView.swift new file mode 100644 index 0000000..eae1ad7 --- /dev/null +++ b/ReaderHive/Class/Home/V/NRStarGradeView.swift @@ -0,0 +1,118 @@ +// +// NRStarGradeView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/2. +// + +import UIKit +import SnapKit +import Cosmos + +class NRStarGradeView: UIView { + + //0-10 + var grade: CGFloat { + set { + cosmosView.rating = newValue + } + get { + return cosmosView.rating + } + } + + var text: String? { + set { + cosmosView.text = newValue + } + get { + return cosmosView.text + } + } + + var filledImage: UIImage? = UIImage(named: "star_icon_02") { + didSet { + settings.filledImage = filledImage + settings.starSize = Double(settings.filledImage?.size.width ?? 0) + cosmosView.settings = settings + } + } + + var emptyImage: UIImage? = UIImage(named: "star_icon_03") { + didSet { + settings.emptyImage = emptyImage + cosmosView.settings = settings + } + } + + ///是否允许打分 + var updateOnTouch: Bool = true { + didSet { +// settings.updateOnTouch = updateOnTouch +// cosmosView.settings = settings + self.isUserInteractionEnabled = updateOnTouch + } + } + + var fillMode: StarFillMode = .precise { + didSet { + settings.fillMode = fillMode + cosmosView.settings = settings + } + } + + var didTouch: ((Double)->())? + + var didFinishTouching: ((Double)->())? + + private lazy var settings: CosmosSettings = { + var settings = CosmosSettings.default + settings.filledImage = filledImage + settings.emptyImage = emptyImage + settings.starSize = Double(settings.filledImage?.size.width ?? 0) + settings.starMargin = 4 + settings.fillMode = fillMode + settings.textFont = .font(ofSize: 12, weight: .regular) + settings.textColor = .black + settings.textMargin = 4 + settings.minTouchRating = 1 + settings.updateOnTouch = true + return settings + }() + + private lazy var cosmosView: CosmosView = { + let view = CosmosView(settings: settings) + view.didTouchCosmos = { [weak self] rating in + self?.didTouch?(rating) + } + view.didFinishTouchingCosmos = { [weak self] rating in + self?.didFinishTouching?(rating) + } + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + nr_setupUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + nr_setupUI() + } + + + +} + +extension NRStarGradeView { + + private func nr_setupUI() { + addSubview(cosmosView) + + cosmosView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + +} diff --git a/ReaderHive/Class/Home/VM/NRHomeNovelViewModel.swift b/ReaderHive/Class/Home/VM/NRHomeNovelViewModel.swift new file mode 100644 index 0000000..fe50680 --- /dev/null +++ b/ReaderHive/Class/Home/VM/NRHomeNovelViewModel.swift @@ -0,0 +1,74 @@ +// +// NRHomeNovelViewModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/2. +// + +import UIKit + +class NRHomeNovelViewModel: NSObject { + + lazy var headerDataArr: [NRHomeNovelModuleItem] = [] + + func requestHomeData() async { + guard let list = await NRHomeAPI.requestHomeData() else { return } + + var item1: NRHomeNovelModuleItem? + + var item2: NRHomeNovelModuleItem? + + var item3: NRHomeNovelModuleItem? + + var item4: NRHomeNovelModuleItem? + + var item5: NRHomeNovelModuleItem? + + var item6: NRHomeNovelModuleItem? + + var item7: NRHomeNovelModuleItem? + + list.forEach { item in + if item.module_key == .banner, let list = item.list, list.count > 0 { + item1 = item + } else if item.module_key == .new_recommand, let list = item.list, list.count > 0 { + item2 = item + } else if item.module_key == .get_details_recommand, let list = item.list, list.count > 0 { + item3 = item + } else if item.module_key == .highest_payment_hot_video, let list = item.list, list.count > 0 { + item4 = item + } else if item .module_key == .v3_recommand, let list = item.list, list.count > 0 { + item5 = item + } else if item .module_key == .category_navigation, let list = item.categoryList, list.count > 0 { + item6 = item + } else if item .module_key == .week_ranking, let list = item.list, list.count > 0 { + item7 = item + } + } + + headerDataArr.removeAll() + + if let item = item1 { + headerDataArr.append(item) + } + if let item = item2 { + headerDataArr.append(item) + } + if let item = item3 { + headerDataArr.append(item) + } + if let item = item4 { + headerDataArr.append(item) + } + if let item = item5 { + headerDataArr.append(item) + } + if let item = item6 { + headerDataArr.append(item) + } + if let item = item7 { + headerDataArr.append(item) + } + } + +} diff --git a/ReaderHive/Class/Home/VM/NRSearchViewModel.swift b/ReaderHive/Class/Home/VM/NRSearchViewModel.swift new file mode 100644 index 0000000..d2ebe32 --- /dev/null +++ b/ReaderHive/Class/Home/VM/NRSearchViewModel.swift @@ -0,0 +1,45 @@ +// +// NRSearchViewModel.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit + +class NRSearchViewModel: NSObject { + + static let searchRecordUserDefaultKey = "NRSearchViewModel.searchRecordUserDefaultKey" + + @objc dynamic private(set) var recordList: [String] = (UserDefaults.standard.object(forKey: NRSearchViewModel.searchRecordUserDefaultKey) as? [String]) ?? [] + + + + func addSearchRecord(text: String) { + guard !text.isEmpty else { return } + var list = recordList + + for (index, value) in list.enumerated() { + if value == text { + list.remove(at: index) + break + } + } + + list.insert(text, at: 0) + + if list.count > 10 { + list.removeLast() + } + recordList = list + + UserDefaults.standard.set(list, forKey: NRSearchViewModel.searchRecordUserDefaultKey) + } + + func clearSearchRecord() { + recordList.removeAll() + UserDefaults.standard.set(recordList, forKey: NRSearchViewModel.searchRecordUserDefaultKey) + } + + +} diff --git a/ReaderHive/Class/Me/M/NRLanguageModel.swift b/ReaderHive/Class/Me/M/NRLanguageModel.swift new file mode 100644 index 0000000..bc1c1e0 --- /dev/null +++ b/ReaderHive/Class/Me/M/NRLanguageModel.swift @@ -0,0 +1,21 @@ +// +// NRLanguageModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/8. +// + +import UIKit +import SmartCodable + +class NRLanguageModel: NSObject, SmartCodable { + + required override init() { } + + var id: String? + var cn_name: String? + var show_name: String? + var lang_key: String? + var is_up_to_list: String? + +} diff --git a/ReaderHive/Class/Me/M/NRMeItem.swift b/ReaderHive/Class/Me/M/NRMeItem.swift new file mode 100644 index 0000000..752c961 --- /dev/null +++ b/ReaderHive/Class/Me/M/NRMeItem.swift @@ -0,0 +1,25 @@ +// +// NRMeItem.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit + +struct NRMeItem { + + enum ItemType { + case history + case language + case about + case settings + case web + } + + var type: ItemType + var icon: UIImage? + var title: String + var url: String? + +} diff --git a/ReaderHive/Class/Me/V/NRAboutCell.swift b/ReaderHive/Class/Me/V/NRAboutCell.swift new file mode 100644 index 0000000..8071753 --- /dev/null +++ b/ReaderHive/Class/Me/V/NRAboutCell.swift @@ -0,0 +1,49 @@ +// +// NRAboutCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import SnapKit + +class NRAboutCell: NRTableViewCell { + + var item: NRMeItem? { + didSet { + titleLabel.text = item?.title + } + } + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + nr_indicatorImageView.image = UIImage(named: "arrow_right_icon_06") + + contentView.addSubview(titleLabel) + contentView.addSubview(nr_indicatorImageView) + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(16) + } + + nr_indicatorImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-16) + } + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/ReaderHive/Class/Me/V/NRAboutHeaderView.swift b/ReaderHive/Class/Me/V/NRAboutHeaderView.swift new file mode 100644 index 0000000..1f0c678 --- /dev/null +++ b/ReaderHive/Class/Me/V/NRAboutHeaderView.swift @@ -0,0 +1,82 @@ +// +// NRAboutHeaderView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import SnapKit + +class NRAboutHeaderView: UIView { + + + + private lazy var appLogoView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "logo_icon_01")) + imageView.layer.cornerRadius = 8 + imageView.layer.masksToBounds = true + imageView.isUserInteractionEnabled = true + let tap = UITapGestureRecognizer(target: self, action: #selector(handleLogoImageView)) + imageView.addGestureRecognizer(tap) + return imageView + }() + + private lazy var nameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .semibold) + label.textColor = .black + label.text = kNRAPPName + return label + }() + + private lazy var versionLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = ._999999 + label.text = "Version \(kNRAPPVersion)" + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func handleLogoImageView() { + guard let url = URL(string: NRWebBaseURL) else { return } + UIApplication.shared.open(url) + } + +} + +extension NRAboutHeaderView { + + private func nr_setupUI() { + addSubview(appLogoView) + addSubview(nameLabel) + addSubview(versionLabel) + + appLogoView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(24) + make.width.height.equalTo(72) + } + + nameLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(appLogoView.snp.bottom).offset(12) + } + + versionLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(nameLabel.snp.bottom).offset(5) + } + + } + +} diff --git a/ReaderHive/Class/Me/V/NRLanguageCell.swift b/ReaderHive/Class/Me/V/NRLanguageCell.swift new file mode 100644 index 0000000..023b24d --- /dev/null +++ b/ReaderHive/Class/Me/V/NRLanguageCell.swift @@ -0,0 +1,76 @@ +// +// NRLanguageCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/8. +// + +import UIKit +import SnapKit + +class NRLanguageCell: UICollectionViewCell { + + var model: NRLanguageModel? { + didSet { + titleLabel.text = model?.show_name + } + } + + var nr_isSelected: Bool = false { + didSet { + if nr_isSelected { + contentView.layer.borderColor = UIColor.F_9710_D.cgColor + selectedImageView.image = UIImage(named: "checked_icon_01_selected") + } else { + contentView.layer.borderColor = UIColor.clear.cgColor + selectedImageView.image = UIImage(named: "checked_icon_01") + } + } + } + + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + return label + }() + + private lazy var selectedImageView = UIImageView() + + + override init(frame: CGRect) { + super.init(frame: frame) + + contentView.backgroundColor = .F_2_EFEE + contentView.layer.cornerRadius = 8 + contentView.layer.masksToBounds = true + contentView.layer.borderWidth = 1 + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRLanguageCell { + + private func nr_setupUI() { + contentView.addSubview(titleLabel) + contentView.addSubview(selectedImageView) + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(12) + } + + selectedImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-12) + } + } + +} diff --git a/ReaderHive/Class/Me/V/NRMeCell.swift b/ReaderHive/Class/Me/V/NRMeCell.swift new file mode 100644 index 0000000..6c3c115 --- /dev/null +++ b/ReaderHive/Class/Me/V/NRMeCell.swift @@ -0,0 +1,68 @@ +// +// NRMeCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import SnapKit + +class NRMeCell: NRTableViewCell { + + var item: NRMeItem? { + didSet { + iconImageView.image = item?.icon + titleLabel.text = item?.title + } + } + + private lazy var iconImageView: UIImageView = { + let imageView = UIImageView() + return imageView + }() + + 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) + + self.contentView.backgroundColor = .white + + nr_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension NRMeCell { + + private func nr_setupUI() { + contentView.addSubview(iconImageView) + contentView.addSubview(titleLabel) + contentView.addSubview(nr_indicatorImageView) + + iconImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(12) + } + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(60) + } + + nr_indicatorImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-12) + } + } + +} diff --git a/ReaderHive/Class/Me/V/NRMeCoinsContentView.swift b/ReaderHive/Class/Me/V/NRMeCoinsContentView.swift new file mode 100644 index 0000000..45b1d93 --- /dev/null +++ b/ReaderHive/Class/Me/V/NRMeCoinsContentView.swift @@ -0,0 +1,103 @@ +// +// NRMeCoinsContentView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/8. +// + +import UIKit +import SnapKit + +class NRMeCoinsContentView: UIView { + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: 60) + } + + var userInfo: NRUserInfo? { + didSet { + coinsView.count = userInfo?.coin_left_total + sendCoinsView.count = userInfo?.send_coin_left_total + } + } + + private lazy var coinsView: NRMeCoinsView = { + let view = NRMeCoinsView() + view.title = "Coins".localized + return view + }() + + private lazy var sendCoinsView: NRMeCoinsView = { + let view = NRMeCoinsView() + view.title = "Bonus".localized + return view + }() + + private lazy var lineView: UIView = { + let view = UIView() + view.backgroundColor = .F_2_EFEE + return view + }() + + private lazy var topUpButton: UIButton = { + let button = NRGradientButton(type: .custom) + 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 = 18 + button.layer.masksToBounds = true + button.setTitle("Top Up".localized, for: .normal) + button.setTitleColor(UIColor.white, for: .normal) + button.titleLabel?.font = .font(ofSize: 14, weight: .medium) + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = .white + layer.cornerRadius = 8 + layer.masksToBounds = true + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRMeCoinsContentView { + + private func nr_setupUI() { + addSubview(coinsView) + addSubview(sendCoinsView) + addSubview(lineView) + addSubview(topUpButton) + + coinsView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(12) + make.centerY.equalToSuperview() + } + + sendCoinsView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(lineView.snp.right).offset(20) + } + + lineView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(coinsView.snp.right).offset(20) + make.width.equalTo(1) + make.height.equalTo(24) + } + + topUpButton.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-12) + make.width.equalTo(100) + make.height.equalTo(36) + } + } + +} diff --git a/ReaderHive/Class/Me/V/NRMeCoinsView.swift b/ReaderHive/Class/Me/V/NRMeCoinsView.swift new file mode 100644 index 0000000..a9f55de --- /dev/null +++ b/ReaderHive/Class/Me/V/NRMeCoinsView.swift @@ -0,0 +1,76 @@ +// +// NRMeCoinsView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/8. +// + +import UIKit +import SnapKit + +class NRMeCoinsView: UIView { + + var title: String? { + didSet { + titleLabel.text = title + } + } + + var count: Int? { + didSet { + coinLabel.text = "\(count ?? 0)" + } + } + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .semibold) + label.textColor = .black.withAlphaComponent(0.5) + return label + }() + + private lazy var iconImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "coins_icon_01")) + return imageView + }() + + private lazy var coinLabel: 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(iconImageView) + addSubview(coinLabel) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview() + make.top.equalToSuperview() + make.right.lessThanOrEqualToSuperview() + } + + iconImageView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.bottom.equalToSuperview() + make.top.equalTo(titleLabel.snp.bottom).offset(5) + } + + coinLabel.snp.makeConstraints { make in + make.left.equalTo(iconImageView.snp.right).offset(4) + make.centerY.equalTo(iconImageView) + make.right.lessThanOrEqualToSuperview() + } + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/ReaderHive/Class/Me/V/NRMeHeaderView.swift b/ReaderHive/Class/Me/V/NRMeHeaderView.swift new file mode 100644 index 0000000..8ece14c --- /dev/null +++ b/ReaderHive/Class/Me/V/NRMeHeaderView.swift @@ -0,0 +1,146 @@ +// +// NRMeHeaderView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import SnapKit + +class NRMeHeaderView: UITableViewHeaderFooterView { + +// var contentHeight: CGFloat = 200 + + private lazy var avatarImageView: UIImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 30 + imageView.layer.borderWidth = 1 + imageView.layer.borderColor = UIColor.black.withAlphaComponent(0.15).cgColor + return imageView + }() + + private lazy var nickLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 18, weight: .semibold) + label.textColor = .black + return label + }() + + private lazy var idBgView: UIView = { + let view = UIView() + view.backgroundColor = .F_2_EFEE + view.layer.cornerRadius = 9 + view.layer.masksToBounds = true + return view + }() + + private lazy var idLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 10, weight: .regular) + label.textColor = .black + return label + }() + + 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") + })) + button.setImage(UIImage(named: "copy_icon_01"), for: .normal) + return button + }() + + private lazy var stackView: UIStackView = { + let view = UIStackView() + view.axis = .vertical + view.spacing = 16 + return view + }() + + private lazy var coinsView: NRMeCoinsContentView = { + let view = NRMeCoinsContentView() + return view + }() + + deinit { + NotificationCenter.default.removeObserver(self) + } + + 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) + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func userInfoUpdateNotification() { + guard let userInfo = NRLoginManager.manager.userInfo else { return } + + avatarImageView.nr_setImage(userInfo.avator) + nickLabel.text = userInfo.family_name?.isEmpty != false ? "Visitor".localized : userInfo.family_name + idLabel.text = "ID:\(userInfo.customer_id ?? "")" + + coinsView.userInfo = userInfo + } + +} + +extension NRMeHeaderView { + + private func nr_setupUI() { + contentView.addSubview(avatarImageView) + contentView.addSubview(nickLabel) + contentView.addSubview(idBgView) + idBgView.addSubview(idLabel) + idBgView.addSubview(copyButton) +// 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) + } + + nickLabel.snp.makeConstraints { make in + make.left.equalTo(avatarImageView.snp.right).offset(16) + make.top.equalTo(avatarImageView).offset(5) + } + + idBgView.snp.makeConstraints { make in + make.left.equalTo(nickLabel) + make.bottom.equalTo(avatarImageView).offset(-5) + make.height.equalTo(18) + } + + idLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(8) + } + + copyButton.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.right.equalToSuperview().offset(-8) + 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) +// } + } + +} diff --git a/ReaderHive/Class/Me/V/NRNovelHistoryCell.swift b/ReaderHive/Class/Me/V/NRNovelHistoryCell.swift new file mode 100644 index 0000000..4f7dd70 --- /dev/null +++ b/ReaderHive/Class/Me/V/NRNovelHistoryCell.swift @@ -0,0 +1,134 @@ +// +// NRNovelHistoryCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import SnapKit + +class NRNovelHistoryCell: UICollectionViewCell { + + var model: NRNovelModel? { + didSet { + coverImageView.nr_setImage(model?.image_url) + titleLabel.text = model?.name + + chLabel.text = "Ch.##".localizedReplace(text: "\(model?.current_episode ?? 0)") + " / " + "Ch.##".localizedReplace(text: "\(model?.episode_total ?? 0)") + + if let text = model?.category?.first, text.count > 0 { + categoryView.text = text + categoryView.isHidden = false + } else { + categoryView.isHidden = true + } + + collectButton.isSelected = model?.is_collect ?? false + } + } + + private lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + return imageView + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + return label + }() + + private lazy var chLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black.withAlphaComponent(0.25) + return label + }() + + private lazy var categoryView: NRHomeCategoryTagView = { + let view = NRHomeCategoryTagView() + return view + }() + + private lazy var collectButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "collect_icon_04"), for: .normal) + button.setImage(UIImage(named: "collect_icon_04_selected"), for: .selected) + button.setImage(UIImage(named: "collect_icon_04_selected"), for: [.selected, .highlighted]) + button.addAction(UIAction(handler: { [weak self] _ in + guard let self = self else { return } + let isCollect = !(self.model?.is_collect ?? false) + + Task { + await NRNovelAPI.requestCollect(isCollect: isCollect, id: self.model?.id ?? "") + } + + }), for: .touchUpInside) + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + NotificationCenter.default.addObserver(self, selector: #selector(updateCollectStateNotification), name: NRNovelAPI.updateCollectStateNotification, object: nil) + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func updateCollectStateNotification(sender: Notification) { + guard let userInfo = sender.userInfo else { return } + guard let id = userInfo["id"] as? String else { return } + guard let state = userInfo["state"] as? Bool else { return } + guard id == self.model?.id else { return } + + self.model?.is_collect = state + collectButton.isSelected = model?.is_collect ?? false + } + +} + +extension NRNovelHistoryCell { + + private func nr_setupUI() { + contentView.addSubview(coverImageView) + contentView.addSubview(titleLabel) + contentView.addSubview(chLabel) + contentView.addSubview(categoryView) + contentView.addSubview(collectButton) + + coverImageView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.bottom.equalToSuperview() + make.width.equalTo(54) + } + + titleLabel.snp.makeConstraints { make in + make.left.equalTo(coverImageView.snp.right).offset(12) + make.top.equalToSuperview().offset(8) + make.right.lessThanOrEqualToSuperview().offset(-60) + } + + chLabel.snp.makeConstraints { make in + make.left.equalTo(titleLabel) + make.top.equalTo(titleLabel.snp.bottom).offset(7) + } + + categoryView.snp.makeConstraints { make in + make.left.equalTo(titleLabel) + make.bottom.equalToSuperview().offset(-7.5) + } + + collectButton.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-16) + } + } + +} diff --git a/ReaderHive/Class/Me/VC/NRAboutViewController.swift b/ReaderHive/Class/Me/VC/NRAboutViewController.swift new file mode 100644 index 0000000..f33da63 --- /dev/null +++ b/ReaderHive/Class/Me/VC/NRAboutViewController.swift @@ -0,0 +1,96 @@ +// +// NRAboutViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import SnapKit + +class NRAboutViewController: NRViewController { + + + private lazy var dataArr: [NRMeItem] = { + let arr = [ + NRMeItem(type: .web, title: "Privacy Policy".localized, url: kNRPrivacyPolicyWebUrl), + NRMeItem(type: .web, title: "User Agreement".localized, url: kNRUserAgreementWebUrl), + NRMeItem(type: .web, title: "Visit Website".localized, url: NRWebBaseURL), + ] + return arr + }() + + private lazy var headerView: NRAboutHeaderView = { + let view = NRAboutHeaderView(frame: .init(x: 0, y: 0, width: UIScreen.width, height: 175)) + return view + }() + + private lazy var tableView: NRTableView = { + let tableView = NRTableView(frame: .zero, style: .plain) + tableView.delegate = self + tableView.dataSource = self + tableView.rowHeight = 50 + tableView.separatorStyle = .none + tableView.register(NRAboutCell.self, forCellReuseIdentifier: "cell") + return tableView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.edgesForExtendedLayout = .top + self.backgroundImageView.isHidden = true + self.title = "About".localized + + 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 NRAboutViewController { + + private func nr_setupUI() { + tableView.tableHeaderView = self.headerView + + view.addSubview(tableView) + + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + } + } + +} + +//MARK: UITableViewDelegate UITableViewDataSource +extension NRAboutViewController: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return dataArr.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! NRAboutCell + cell.item = dataArr[indexPath.row] + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let item = dataArr[indexPath.row] + guard let webUrl = item.url else { return } + + + let vc = NRWebViewController() + vc.title = item.title + vc.webUrl = webUrl + self.navigationController?.pushViewController(vc, animated: true) + } +} diff --git a/ReaderHive/Class/Me/VC/NRHistoryViewController.swift b/ReaderHive/Class/Me/VC/NRHistoryViewController.swift new file mode 100644 index 0000000..d483845 --- /dev/null +++ b/ReaderHive/Class/Me/VC/NRHistoryViewController.swift @@ -0,0 +1,41 @@ +// +// NRHistoryViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import SnapKit + +class NRHistoryViewController: NRViewController { + + + private lazy var vc = NRNovelHistoryViewController() + + override func viewDidLoad() { + super.viewDidLoad() + self.title = "History".localized + self.backgroundImageView.isHidden = true + configNavigationBack("arrow_left_icon_05") + + addChild(vc) + + view.addSubview(vc.view) + + vc.view.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + } + } + + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + self.nr_setNavigationStyle(titleColor: UINavigationBar.titleBlackColor) + } + + + +} diff --git a/ReaderHive/Class/Me/VC/NRLanguageViewController.swift b/ReaderHive/Class/Me/VC/NRLanguageViewController.swift new file mode 100644 index 0000000..4c7c5b1 --- /dev/null +++ b/ReaderHive/Class/Me/VC/NRLanguageViewController.swift @@ -0,0 +1,103 @@ +// +// NRLanguageViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/8. +// + +import UIKit +import SnapKit + +class NRLanguageViewController: NRViewController { + + private lazy var dataArr: [NRLanguageModel] = [] + +// private lazy var tableView: NRTableView = { +// let tableView = NRTableView(frame: .zero, style: .insetGrouped) +// tableView.delegate = self +// tableView.dataSource = self +// tableView.register(NRLanguageCell.self, forCellReuseIdentifier: "cell") +// return tableView +// }() + + private lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.itemSize = .init(width: UIScreen.width - 32, height: 60) + layout.minimumLineSpacing = 12 + return layout + }() + + private lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.contentInset = .init(top: 24, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) + collectionView.register(NRLanguageCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.title = "Language".localized + self.backgroundImageView.isHidden = true + configNavigationBack("arrow_left_icon_05") + + nr_setupUI() + + Task { + await requestDataArr() + } + } + + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + self.nr_setNavigationStyle(titleColor: UINavigationBar.titleBlackColor) + } + +} + +extension NRLanguageViewController { + + private func nr_setupUI() { + view.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + make.bottom.equalToSuperview() + } + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRLanguageViewController: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let model = self.dataArr[indexPath.row] + + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRLanguageCell + cell.model = self.dataArr[indexPath.row] + cell.nr_isSelected = model.lang_key == NRLocalizedManager.shared.currentLocalizedKey + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.dataArr.count + } + +} + +extension NRLanguageViewController { + + private func requestDataArr() async { + guard let list = await NRSettingAPI.requestLanguageList() else { return } + + self.dataArr = list + + self.collectionView.reloadData() + } + +} diff --git a/ReaderHive/Class/Me/VC/NRMeViewController.swift b/ReaderHive/Class/Me/VC/NRMeViewController.swift new file mode 100644 index 0000000..87c6b90 --- /dev/null +++ b/ReaderHive/Class/Me/VC/NRMeViewController.swift @@ -0,0 +1,108 @@ +// +// NRMeViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/3. +// + +import UIKit +import SnapKit + +class NRMeViewController: NRViewController { + + private lazy var dataArr: [NRMeItem] = { + 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) + ] + return arr + }() + + private lazy var tableView: NRTableView = { + let tableView = NRTableView(frame: .zero, style: .insetGrouped) + tableView.delegate = self + tableView.dataSource = self + tableView.separatorStyle = .none + tableView.sectionHeaderHeight = UITableView.automaticDimension + tableView.rowHeight = 60 + tableView.register(NRMeCell.self, forCellReuseIdentifier: "cell") + tableView.register(NRMeHeaderView.self, forHeaderFooterViewReuseIdentifier: "header") + return tableView + }() + + override func viewDidLoad() { + super.viewDidLoad() + + nr_setupUI() + + } + + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + Task { + await NRLoginManager.manager.updateUserInfo() + } + } + +} + +extension NRMeViewController { + + private func nr_setupUI() { + view.addSubview(tableView) + + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.safeTop) + } + } + +} + +//MARK: UITableViewDelegate UITableViewDataSource +extension NRMeViewController: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! NRMeCell + 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? NRMeHeaderView + return view + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let item = dataArr[indexPath.row] + + switch item.type { + case .about: + let vc = NRAboutViewController() + self.navigationController?.pushViewController(vc, animated: true) + + case .history: + let vc = NRHistoryViewController() + self.navigationController?.pushViewController(vc, animated: true) + + case .language: + let vc = NRLanguageViewController() + self.navigationController?.pushViewController(vc, animated: true) + + default: + break + } + } +} diff --git a/ReaderHive/Class/Me/VC/NRNovelHistoryViewController.swift b/ReaderHive/Class/Me/VC/NRNovelHistoryViewController.swift new file mode 100644 index 0000000..2cd9de7 --- /dev/null +++ b/ReaderHive/Class/Me/VC/NRNovelHistoryViewController.swift @@ -0,0 +1,111 @@ +// +// NRNovelHistoryViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import SnapKit +import LYEmptyView + +class NRNovelHistoryViewController: NRViewController { + + private lazy var dataArr: [NRNovelModel] = [] + private lazy var page = 1 + + private lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.itemSize = .init(width: UIScreen.width, height: 81) + layout.minimumLineSpacing = 16 + return layout + }() + + private lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.ly_emptyView = NREmpty.nr_emptyView() + collectionView.contentInset = .init(top: 15, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) + collectionView.nr_addRefreshHeader(insetTop: collectionView.contentInset.top) { [weak self] in + self?.handleHeaderRefresh(nil) + } + collectionView.nr_addRefreshFooter(insetBottom: collectionView.contentInset.bottom) { [weak self] in + self?.handleFooterRefresh(nil) + } + collectionView.register(NRNovelHistoryCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.backgroundImageView.isHidden = true + self.view.backgroundColor = .clear + + view.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview() + } + + Task { + await requestDataArr(page: 1) + } + + } + + override func handleHeaderRefresh(_ completer: (() -> Void)?) { + Task { + await requestDataArr(page: 1) + self.collectionView.nr_endHeaderRefreshing() + } + } + + override func handleFooterRefresh(_ completer: (() -> Void)?) { + Task { + await requestDataArr(page: self.page + 1) + self.collectionView.nr_endFooterRefreshing() + } + } + + + +} + +//MARK: UICollectionViewDelegate UICollectionViewDelegate +extension NRNovelHistoryViewController: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRNovelHistoryCell + cell.model = self.dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let model = self.dataArr[indexPath.row] + let vc = NRNovelDetailViewController() + vc.novelId = model.id ?? "" + self.navigationController?.pushViewController(vc, animated: true) + } + +} + +extension NRNovelHistoryViewController { + + private func requestDataArr(page: Int) async { + guard let list = await NRNovelAPI.requestHistoryList(page: page) else { return } + if page == 1 { + self.dataArr.removeAll() + } + self.dataArr += list + self.page = page + self.collectionView.reloadData() + + } + +} diff --git a/ReaderHive/Class/MyList/V/NRMyListNovelCell.swift b/ReaderHive/Class/MyList/V/NRMyListNovelCell.swift new file mode 100644 index 0000000..c426970 --- /dev/null +++ b/ReaderHive/Class/MyList/V/NRMyListNovelCell.swift @@ -0,0 +1,126 @@ +// +// NRMyListNovelCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit + +class NRMyListNovelCell: UICollectionViewCell { + + var didClickDelete: ((UICollectionViewCell) -> Void)? + + var nr_isEditing = false { + didSet { + deleteButton.isHidden = !nr_isEditing + } + } + + var model: NRNovelModel? { + didSet { + coverImageView.nr_setImage(model?.image_url) + titleLabel.text = model?.name + chLabel.text = "Ch.##".localizedReplace(text: "\(model?.current_episode ?? 0)") + "/" + "Ch.##".localizedReplace(text: "\(model?.episode_total ?? 0)") + + if model?.tag_type == .new { + markImageView.image = UIImage(named: "new_icon_01") + markImageView.isHidden = false + } else if model?.tag_type == .hot { + markImageView.image = UIImage(named: "hot_icon_03") + markImageView.isHidden = false + } else { + markImageView.isHidden = true + } + } + } + + + lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + imageView.layer.masksToBounds = true + imageView.isUserInteractionEnabled = true + return imageView + }() + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + label.numberOfLines = 2 + return label + }() + + lazy var markImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "new_icon_01")) + return imageView + }() + + lazy var chLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black.withAlphaComponent(0.25) + return label + }() + + lazy var deleteButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.didClickDelete?(self) + })) + button.backgroundColor = .black.withAlphaComponent(0.5) + button.setImage(UIImage(named: "delete_icon_02"), for: .normal) + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + titleLabel.text = "Vanished Without a Word" + chLabel.text = "Ch.1 / Ch.88" + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRMyListNovelCell { + private func nr_setupUI() { + contentView.addSubview(coverImageView) + coverImageView.addSubview(markImageView) + coverImageView.addSubview(deleteButton) + contentView.addSubview(titleLabel) + contentView.addSubview(chLabel) + + + coverImageView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.bottom.equalToSuperview().offset(-68) + } + + markImageView.snp.makeConstraints { make in + make.left.top.equalToSuperview() + } + + deleteButton.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview() + make.right.lessThanOrEqualToSuperview() + make.top.equalTo(coverImageView.snp.bottom).offset(8) + } + + chLabel.snp.makeConstraints { make in + make.left.equalToSuperview() + make.bottom.equalToSuperview().offset(-3) + } + + } +} diff --git a/ReaderHive/Class/MyList/VC/NRMyListNovelViewController.swift b/ReaderHive/Class/MyList/VC/NRMyListNovelViewController.swift new file mode 100644 index 0000000..0b18572 --- /dev/null +++ b/ReaderHive/Class/MyList/VC/NRMyListNovelViewController.swift @@ -0,0 +1,149 @@ +// +// NRMyListNovelViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit +import LYEmptyView + +class NRMyListNovelViewController: NRViewController { + + + override var nr_isEditing: Bool { + didSet { + self.collectionView.reloadData() + } + } + + lazy var dataArr: [NRNovelModel] = [] + lazy var page = 1 + + lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let itemWidth = (UIScreen.width - 32 - 40) / 3 + let itemHeight = 150 / 100 * itemWidth + 68 + + let layout = UICollectionViewFlowLayout() + layout.itemSize = .init(width: floor(itemWidth), height: itemHeight) + layout.minimumInteritemSpacing = 20 + layout.minimumLineSpacing = 18 + layout.sectionInset = .init(top: 0, left: 16, bottom: 0, right: 16) + return layout + }() + + lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.ly_emptyView = NREmpty.nr_emptyView() + collectionView.contentInset = .init(top: 0, left: 0, bottom: 10, right: 0) + collectionView.register(NRMyListNovelCell.self, forCellWithReuseIdentifier: "cell") + collectionView.nr_addRefreshHeader { [weak self] in + self?.handleHeaderRefresh(nil) + } + collectionView.nr_addRefreshFooter(insetBottom: collectionView.contentInset.bottom) { [weak self] in + self?.handleFooterRefresh(nil) + } + return collectionView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.backgroundImageView.isHidden = true + self.view.backgroundColor = .clear + + nr_setupUI() + Task { + await requestDataArr(page: 1) + } + } + + override func handleHeaderRefresh(_ completer: (() -> Void)?) { + Task { + await requestDataArr(page: 1) + self.collectionView.nr_endHeaderRefreshing() + } + } + + override func handleFooterRefresh(_ completer: (() -> Void)?) { + Task { + await requestDataArr(page: self.page + 1) + self.collectionView.nr_endFooterRefreshing() + } + } + +} + +extension NRMyListNovelViewController { + + private func nr_setupUI() { + view.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(10) + } + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRMyListNovelViewController: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRMyListNovelCell + cell.model = dataArr[indexPath.row] + cell.nr_isEditing = self.nr_isEditing + cell.didClickDelete = { [weak self] cell in + guard let self = self else { return } + Task { + await self.deleteCollect(indexPath: indexPath) + } + + } + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard !self.nr_isEditing else { return } + let model = self.dataArr[indexPath.row] + + let vc = NRNovelDetailViewController() + vc.novelId = model.short_play_id ?? "" + self.navigationController?.pushViewController(vc, animated: true) + } +} + +extension NRMyListNovelViewController { + + func requestDataArr(page: Int) async { + guard let list = await NRNovelAPI.requestCollectList(page: page) else { return } + + if page == 1 { + self.dataArr.removeAll() + } + self.dataArr += list + self.page = page + + await MainActor.run { + self.collectionView.reloadData() + } + } + + func deleteCollect(indexPath: IndexPath) async { + let model = self.dataArr[indexPath.row] + guard let id = model.short_play_id else { return } + + await NRNovelAPI.requestCollect(isCollect: false, id: id, chapterId: model.short_play_video_id) + + await self.requestDataArr(page: 1) + } + +} + diff --git a/ReaderHive/Class/MyList/VC/NRMyListViewController.swift b/ReaderHive/Class/MyList/VC/NRMyListViewController.swift new file mode 100644 index 0000000..6d9dcd0 --- /dev/null +++ b/ReaderHive/Class/MyList/VC/NRMyListViewController.swift @@ -0,0 +1,73 @@ +// +// NRMyListViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit + +class NRMyListViewController: NRViewController { + + + override var nr_isEditing: Bool { + didSet { + editButton.isSelected = nr_isEditing + novelVC.nr_isEditing = nr_isEditing + } + } + + lazy var novelVC = NRMyListNovelViewController() + + lazy var editButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.nr_isEditing = !self.nr_isEditing + })) + button.setImage(UIImage(named: "edit_icon_01"), for: .normal) + button.setImage(UIImage(named: "edit_done_icon_01"), for: .selected) + button.setImage(UIImage(named: "edit_done_icon_01"), for: [.selected, .highlighted]) + return button + }() + + override func viewDidLoad() { + super.viewDidLoad() + + nr_setupUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + self.nr_isEditing = false + } + + +} + +extension NRMyListViewController { + + private func nr_setupUI() { + view.addSubview(editButton) + addChild(novelVC) + view.addSubview(novelVC.view) + + editButton.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-6) + make.top.equalToSuperview().offset(UIScreen.safeTop) + make.width.equalTo(44) + make.height.equalTo(44) + } + + novelVC.view.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + } + } + +} diff --git a/ReaderHive/Class/Novel/M/NRCategoryModel.swift b/ReaderHive/Class/Novel/M/NRCategoryModel.swift new file mode 100644 index 0000000..5383a63 --- /dev/null +++ b/ReaderHive/Class/Novel/M/NRCategoryModel.swift @@ -0,0 +1,24 @@ +// +// NRCategoryModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/2. +// + +import UIKit +import SmartCodable + +class NRCategoryModel: NSObject, SmartCodable { + + required override init() { } + + var id: String? + var name: String? + + static func mappingForKey() -> [SmartKeyTransformer]? { + return [ + CodingKeys.id <--- ["id", "category_id"], + CodingKeys.name <--- ["name", "category_name"] + ] + } +} diff --git a/ReaderHive/Class/Novel/M/NRNovelModel.swift b/ReaderHive/Class/Novel/M/NRNovelModel.swift new file mode 100644 index 0000000..eb64d79 --- /dev/null +++ b/ReaderHive/Class/Novel/M/NRNovelModel.swift @@ -0,0 +1,84 @@ +// +// NRNovelModel.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit +import SmartCodable + +class NRNovelModel: NSObject, SmartCodable { + + enum TagType: String, SmartCaseDefaultable { + case hot = "hot" + case new = "new" + } + + required override init() { } + + + var id: String? + var short_play_id: String? + var short_play_video_id: String? + var name: String? + var nr_description: String? + var image_url: String? + var horizontally_img: String? + var collect_total: Int? + var is_collect: Bool? + var current_page: Int? + var current_episode: Int? + var history_episode: Int? + var process: String? + var category: [String]? + var categoryList: [NRCategoryModel]? + var tag_type: TagType? + ///热度 + var heats: Int? + ///字数 + var words: Int? + ///评分 + var rate: CGFloat? + ///自己的评分,-1标识没有评分 + var self_rate: CGFloat? + var episode_total: Int? + + var watch_total: Int? + + var progress: NRNovelReadRecordModel? + + + static func mappingForKey() -> [SmartKeyTransformer]? { + return [ + CodingKeys.nr_description <--- ["description"], + CodingKeys.rate <--- ["rate", "novel_score"] + ] + } +} + +class NRNovelReadRecordModel: NSObject, SmartCodable, NSSecureCoding { + required override init() { } + + var short_play_video_id: String? + var episode: String? + + var page: Int? + + static var supportsSecureCoding: Bool { + return true + } + + func encode(with coder: NSCoder) { + coder.encode(short_play_video_id, forKey: "short_play_video_id") + coder.encode(episode, forKey: "episode") + coder.encode(page, forKey: "page") + } + + required init?(coder: NSCoder) { + super.init() + short_play_video_id = coder.decodeObject(of: NSString.self, forKey: "short_play_video_id") as? String + episode = coder.decodeObject(of: NSString.self, forKey: "episode") as? String + page = coder.decodeObject(of: NSNumber.self, forKey: "page")?.intValue + } +} diff --git a/ReaderHive/Class/Novel/M/NRReadChapterCatalogModel.swift b/ReaderHive/Class/Novel/M/NRReadChapterCatalogModel.swift new file mode 100644 index 0000000..1e691a3 --- /dev/null +++ b/ReaderHive/Class/Novel/M/NRReadChapterCatalogModel.swift @@ -0,0 +1,36 @@ +// +// NRReadChapterCatalogModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/28. +// + +import UIKit +import SmartCodable + + +///章节目录数据 +class NRReadChapterCatalogModel: NSObject, SmartCodable { + + required override init() { } + + var id: String? + + var episode: String? + var name: String? + //字数 + var duration: Int? + var coins: Int? + var status: Int? + + var is_lock: Bool? + + ///章节详情 + @IgnoredKey + var chapterModel: NRReadChapterModel? + + + + + +} diff --git a/ReaderHive/Class/Novel/M/NRReadChapterModel.swift b/ReaderHive/Class/Novel/M/NRReadChapterModel.swift new file mode 100644 index 0000000..ffb93f1 --- /dev/null +++ b/ReaderHive/Class/Novel/M/NRReadChapterModel.swift @@ -0,0 +1,103 @@ +// +// NRReadChapterModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/28. +// + +import UIKit +import SmartCodable + +class NRReadChapterModel: NSObject, SmartCodable { + required override init() { } + + var id: String? + + ///章节id + var short_play_video_id: String? + ///小说id + var short_play_id: String? + + var episode: String? + var name: String? + //字数 + var duration: Int? + var coins: Int? + var status: Int? + + var nr_description: String? + ///正文 + var novel_txt: String? + + + /// 完整富文本内容 + @IgnoredKey + var fullContent:NSAttributedString! + /// 内容属性变化记录(我这里就只判断内容了字体属性变化了,标题也就跟着变化或者保存变化都无所谓了。如果有需求可以在加上比较标题属性变化) + @IgnoredKey + private var attributes:[NSAttributedString.Key:Any]! = [:] + @IgnoredKey + var pageList: [NRReadPageModel]? + @IgnoredKey + var pageCount: Int = 0 + + + + + static func mappingForKey() -> [SmartKeyTransformer]? { + return [ + CodingKeys.nr_description <--- ["description"], + ] + } + + ///解析数据 + func parser() { + guard novel_txt != nil else { + self.parserEmpty() + return + } + + let tempAttributes = NRNovelReadSetManager.manager.attributes(isTitle: false, isPageing: true) + + guard !NSDictionary(dictionary: attributes).isEqual(to: tempAttributes) else { return } + + attributes = tempAttributes + fullContent = fullContentAttrString() + + var tempPageModel: NRReadPageModel? + if pageList?.last?.pageType != .textPart { + tempPageModel = pageList?.last + } + pageList = NRReadParser.pageing(attrString: fullContent, rect: NRNovelReadSetManager.manager.readRect) + pageCount = pageList?.count ?? 0 + + if let model = tempPageModel { + pageList?.append(model) + } + } + + ///解析一个空白页面 + func parserEmpty() { + fullContent = NSMutableAttributedString(string: "\n\n\n\n\n" + "Not unlocked yet".localized, attributes: NRNovelReadSetManager.manager.attributes(isTitle: true)) + + let pageModel = NRReadPageModel() + pageModel.content = fullContent + + pageList = [pageModel] + pageCount = 1 + } + + + /// 完整内容排版 + private func fullContentAttrString() ->NSMutableAttributedString { + let name = "\n\(self.name ?? "")\n\n" + let titleString = NSMutableAttributedString(string: name, attributes: NRNovelReadSetManager.manager.attributes(isTitle: true)) + + let contentString = NSMutableAttributedString(string: novel_txt ?? "", attributes: NRNovelReadSetManager.manager.attributes(isTitle: false)) + + titleString.append(contentString) + + return titleString + } + +} diff --git a/ReaderHive/Class/Novel/M/NRReadPageModel.swift b/ReaderHive/Class/Novel/M/NRReadPageModel.swift new file mode 100644 index 0000000..c1b1cf7 --- /dev/null +++ b/ReaderHive/Class/Novel/M/NRReadPageModel.swift @@ -0,0 +1,46 @@ +// +// NRReadPageModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/28. +// + +import UIKit + +class NRReadPageModel: NSObject { + + enum PageType { + case textPart + case readFinish + } + + var pageType: PageType = .textPart + + /// 当前页内容 + var content: NSAttributedString = NSAttributedString() + + /// 当前页范围 + var range:NSRange? + + /// 当前页序号 + var page:NSNumber? + + var indexPath: IndexPath = IndexPath(row: 0, section: 0) + + // MARK: 快捷获取 + + /// 获取显示内容(考虑可能会变换字体颜色的情况) + var showContent: NSAttributedString { + let textColor = NRNovelReadSetManager.manager.textColor + let tempShowContent = NSMutableAttributedString(attributedString: content) + tempShowContent.addAttributes([.foregroundColor : textColor], range: NSMakeRange(0, content.length)) + return tempShowContent + } + + + static func createReadFinishModel() -> NRReadPageModel { + let pageModel = NRReadPageModel() + pageModel.pageType = .readFinish + return pageModel + } +} diff --git a/ReaderHive/Class/Novel/M/NRShowRecommendPop.swift b/ReaderHive/Class/Novel/M/NRShowRecommendPop.swift new file mode 100644 index 0000000..cbaa5cb --- /dev/null +++ b/ReaderHive/Class/Novel/M/NRShowRecommendPop.swift @@ -0,0 +1,15 @@ +// +// NRShowRecommendPop.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/5. +// + +import UIKit +import SmartCodable + +class NRShowRecommendPop: NSObject, SmartCodable { + required override init() { } + + var is_pop_up: Bool? +} diff --git a/ReaderHive/Class/Novel/V/NRDetailRechargeView.swift b/ReaderHive/Class/Novel/V/NRDetailRechargeView.swift new file mode 100644 index 0000000..af2a7f3 --- /dev/null +++ b/ReaderHive/Class/Novel/V/NRDetailRechargeView.swift @@ -0,0 +1,23 @@ +// +// NRDetailRechargeView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/8. +// + +import UIKit +import HWPanModal + +class NRDetailRechargeView: NRPanModalContentView { + + override init(frame: CGRect) { + super.init(frame: frame) + self.backgroundColor = .red + + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/ReaderHive/Class/Novel/V/NRNovelDetailBottomView.swift b/ReaderHive/Class/Novel/V/NRNovelDetailBottomView.swift new file mode 100644 index 0000000..4ef0f4e --- /dev/null +++ b/ReaderHive/Class/Novel/V/NRNovelDetailBottomView.swift @@ -0,0 +1,160 @@ +// +// NRNovelDetailBottomView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit +import YYCategories + +class NRNovelDetailBottomView: UIView { + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: UIScreen.safeBottom + 64) + } + + weak var viewModel: NRNovelDetailViewModel? { + didSet { + viewModel?.addObserver(self, forKeyPath: "novelModel", context: nil) + } + } + + lazy var lineView: UIView = { + let view = UIView() + view.backgroundColor = .F_2_EFEE + return view + }() + + lazy var readBgView: UIView = { + let view = NRGradientView() + view.colors = [UIColor.F_3912_F.cgColor, UIColor.FF_4_A_4_A.cgColor, UIColor.FA_9_B_1_F.cgColor] + view.startPoint = .init(x: 0, y: 0.5) + view.endPoint = .init(x: 1, y: 0.5) + view.layer.cornerRadius = 24 + view.layer.masksToBounds = true + return view + }() + + lazy var readButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + guard let id = self.viewModel?.novelId else { return } + let vc = NRNovelReaderViewController() + vc.novelId = id + self.viewController?.navigationController?.pushViewController(vc, animated: true) + })) + button.setTitle("Start Reading".localized, for: .normal) + button.setTitleColor(.white, for: .normal) + button.titleLabel?.font = .font(ofSize: 14, weight: .medium) + return button + }() + + lazy var collectButton: UIButton = { + var configuration = UIButton.Configuration.plain() + configuration.background.backgroundColor = .clear + configuration.contentInsets = .zero + configuration.imagePadding = 4 + configuration.imagePlacement = .top + configuration.attributedTitle = AttributedString("Collect".localized, attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 10, weight: .regular), + .foregroundColor : UIColor.black + ])) + + let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + guard let model = self.viewModel?.novelModel else { return } + let isCollect = !(model.is_collect ?? false) + + Task { + await NRNovelAPI.requestCollect(isCollect: isCollect, id: self.viewModel?.novelId ?? "") + } + })) + + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + if button.isSelected { + button.configuration?.image = UIImage(named: "collect_icon_01_selected") + } else { + button.configuration?.image = UIImage(named: "collect_icon_01") + } + + } + + + return button + }() + + deinit { + self.viewModel?.removeObserver(self, forKeyPath: "novelModel") + NotificationCenter.default.removeObserver(self) + } + + override init(frame: CGRect) { + super.init(frame: frame) + self.backgroundColor = .white + NotificationCenter.default.addObserver(self, selector: #selector(updateCollectStateNotification), name: NRNovelAPI.updateCollectStateNotification, object: nil) + + nr_setupUI() + } + + 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 == "novelModel" { + didChangeCollectState() + } + } + + + @objc private func updateCollectStateNotification(sender: Notification) { + guard let userInfo = sender.userInfo else { return } + guard let id = userInfo["id"] as? String else { return } + guard let state = userInfo["state"] as? Bool else { return } + guard id == self.viewModel?.novelId else { return } + + self.viewModel?.novelModel?.is_collect = state + + didChangeCollectState() + } + + private func didChangeCollectState() { + self.collectButton.isSelected = self.viewModel?.novelModel?.is_collect ?? false + } +} + +extension NRNovelDetailBottomView { + + private func nr_setupUI() { + addSubview(lineView) + addSubview(readBgView) + readBgView.addSubview(readButton) + addSubview(collectButton) + + lineView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.height.equalTo(1) + } + + readBgView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(8) + make.height.equalTo(48) + make.right.equalToSuperview().offset(-16) +// make.left.equalToSuperview().offset(16) + make.left.equalTo(collectButton.snp.right).offset(12) + } + + readButton.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + collectButton.snp.makeConstraints { make in + make.centerX.equalTo(self.snp.left).offset(40) + make.centerY.equalTo(readBgView) + } + } + +} diff --git a/ReaderHive/Class/Novel/V/NRNovelDetailHeaderView+Data.swift b/ReaderHive/Class/Novel/V/NRNovelDetailHeaderView+Data.swift new file mode 100644 index 0000000..97f32c1 --- /dev/null +++ b/ReaderHive/Class/Novel/V/NRNovelDetailHeaderView+Data.swift @@ -0,0 +1,113 @@ +// +// NRNovelDetailHeaderView+Data.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit + + +extension NRNovelDetailHeaderView { + + class DataView: UIView { + + var num: CGFloat = 0 { + didSet { + numLabel.text = NSNumber(value: num).formattedNumber() + } + } + + var title: String? { + didSet { + titleLabel.text = title + } + } + + var icon: UIImage? { + didSet { + iconImageView.image = icon + } + } + + private lazy var contentView: UIView = { + let view = UIView() + return view + }() + + private lazy var numLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .medium) + label.textColor = .F_9710_D + return label + }() + + private lazy var titleView: UIView = { + let view = UIView() + return view + }() + + private lazy var titleContainerView: UIView = { + let view = UIView() + return view + }() + + private lazy var iconImageView: UIImageView = { + let imageView = UIImageView() + return imageView + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black.withAlphaComponent(0.25) + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + addSubview(contentView) + contentView.addSubview(numLabel) + contentView.addSubview(titleContainerView) + titleContainerView.addSubview(iconImageView) + titleContainerView.addSubview(titleLabel) + + contentView.snp.makeConstraints { make in + make.center.equalToSuperview() + } + + numLabel.snp.makeConstraints { make in + make.top.equalToSuperview() + make.centerX.equalToSuperview() + make.right.lessThanOrEqualToSuperview() + } + + titleContainerView.snp.makeConstraints { make in + make.top.equalTo(numLabel.snp.bottom).offset(8) + make.centerX.equalToSuperview() + make.right.lessThanOrEqualToSuperview() + make.bottom.equalToSuperview() + } + + iconImageView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.top.bottom.equalToSuperview() + } + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview() + make.left.equalTo(iconImageView.snp.right).offset(4) + } + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + } + +} diff --git a/ReaderHive/Class/Novel/V/NRNovelDetailHeaderView+NovelCoverInfo.swift b/ReaderHive/Class/Novel/V/NRNovelDetailHeaderView+NovelCoverInfo.swift new file mode 100644 index 0000000..d679a73 --- /dev/null +++ b/ReaderHive/Class/Novel/V/NRNovelDetailHeaderView+NovelCoverInfo.swift @@ -0,0 +1,115 @@ +// +// NRNovelDetailHeaderView+NovelCoverInfo.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + + +import UIKit +import SnapKit + +extension NRNovelDetailHeaderView { + + class NovelCoverInfoView: UIView { + var model: NRNovelModel? { + didSet { + coverImageView.nr_setImage(model?.image_url) + nameLabel.text = model?.name + + stackView.nr_removeAllArrangedSubview() + + model?.category?.forEach { + if let view = self.createCategoryView($0) { + self.stackView.addArrangedSubview(view) + } + } + + if model?.tag_type == .hot { + markImageView.image = UIImage(named: "hot_icon_03") + markImageView.isHidden = false + + } else if model?.tag_type == .new { + markImageView.image = UIImage(named: "new_icon_01") + markImageView.isHidden = false + } else { + markImageView.isHidden = true + } + } + } + + lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + return imageView + }() + + lazy var nameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .white + return label + }() + + lazy var stackView: UIStackView = { + let view = UIStackView() + view.axis = .horizontal + view.spacing = 12 + return view + }() + + lazy var markImageView: UIImageView = { + let imageView = UIImageView() + return imageView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + addSubview(coverImageView) + coverImageView.addSubview(markImageView) + addSubview(nameLabel) + addSubview(stackView) + + coverImageView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(16) + make.width.equalTo(100) + make.height.equalTo(150) + } + + markImageView.snp.makeConstraints { make in + make.left.top.equalToSuperview() + } + + nameLabel.snp.makeConstraints { make in + make.top.equalTo(coverImageView.snp.bottom).offset(8) + make.centerX.equalToSuperview() + make.right.lessThanOrEqualToSuperview().offset(-16) + } + + stackView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().offset(-16) + } + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func createCategoryView(_ text: String?) -> UIView? { + guard let text = text, text.count > 0 else { return nil } + + let view = NRHomeCategoryTagView() + view.text = text + view.backgroundColor = .white.withAlphaComponent(0.15) + view.categoryLabel.textColor = .white + return view + } + + + } + +} diff --git a/ReaderHive/Class/Novel/V/NRNovelDetailHeaderView.swift b/ReaderHive/Class/Novel/V/NRNovelDetailHeaderView.swift new file mode 100644 index 0000000..18d7211 --- /dev/null +++ b/ReaderHive/Class/Novel/V/NRNovelDetailHeaderView.swift @@ -0,0 +1,374 @@ +// +// NRNovelDetailHeaderView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit +import YYCategories + +class NRNovelDetailHeaderView: UIView { + + var contentHeight: CGFloat { + return self.scrollView.contentSize.height + } + + var didChangeHeight: (() -> Void)? + + var model: NRNovelModel? { + didSet { + coverInfoView.model = model + + let des = model?.nr_description ?? "" + + let totalLineNum = des.numberOfLines(self.desLabel.font, .init(width: UIScreen.width - 32, height: .greatestFiniteMagnitude)) + + if Int(totalLineNum) > self.desTotalLineNum { + desMoreButton.isHidden = false + desMoreButton.isSelected = false + desLabel.numberOfLines = self.desTotalLineNum + } else { + desMoreButton.isHidden = true + } + + desLabel.text = des + + + contentsTextLabel.text = "## Chapters".localizedReplace(text: "\(model?.episode_total ?? 0)") + + collectDataView.num = CGFloat(model?.collect_total ?? 0) + heatsDataView.num = CGFloat(model?.heats ?? 0) + wordDataView.num = CGFloat(model?.words ?? 0) + rateDataView.num = model?.rate ?? 0 + } + } + + private let desTotalLineNum = 7 + + private let lineColor: UIColor = .F_2_EFEE + + private lazy var scrollView: NRScrollView = { + let scrollView = NRScrollView() + scrollView.showsVerticalScrollIndicator = false + scrollView.isScrollEnabled = false + scrollView.addObserver(self, forKeyPath: "contentSize", context: nil) + return scrollView + }() + + private lazy var contentView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.nr_setRoundedCorner(topLeft: 16, topRight: 16, bottomLeft: 0, bottomRight: 0) + return view + }() + + private lazy var coverInfoView: NovelCoverInfoView = { + let view = NovelCoverInfoView() + return view + }() + + private lazy var collectDataView: DataView = { + let view = DataView() + view.num = 0 + view.icon = UIImage(named: "collect_icon_02") + view.title = "Collects".localized + return view + }() + + private lazy var heatsDataView: DataView = { + let view = DataView() + view.num = 0 + view.icon = UIImage(named: "hot_icon_04") + view.title = "Heats".localized + return view + }() + + private lazy var wordDataView: DataView = { + let view = DataView() + view.num = 0 + view.icon = UIImage(named: "word_icon_01") + view.title = "Words".localized + return view + }() + + private lazy var rateDataView: DataView = { + let view = DataView() + view.num = 0 + view.icon = UIImage(named: "rate_icon_01") + view.title = "Rate".localized + return view + }() + + private lazy var dataLineView1: UIView = { + let view = UIView() + view.backgroundColor = self.lineColor + return view + }() + + private lazy var dataLineView2: UIView = { + let view = UIView() + view.backgroundColor = self.lineColor + return view + }() + + private lazy var dataLineView3: UIView = { + let view = UIView() + view.backgroundColor = self.lineColor + return view + }() + + private lazy var lineView1: UIView = { + let view = UIView() + view.backgroundColor = self.lineColor + return view + }() + + private lazy var desTitleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .semibold) + label.textColor = .black + label.text = "Synopsis".localized + return label + }() + + private lazy var desMoreButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.desMoreButton.isSelected = !self.desMoreButton.isSelected + if self.desMoreButton.isSelected { + self.desLabel.numberOfLines = 0 + } else { + self.desLabel.numberOfLines = desTotalLineNum + } + })) + button.isHidden = true + button.setImage(UIImage(named: "arrow_bown_icon_01"), for: .normal) + button.setImage(UIImage(named: "arrow_right_icon_01"), for: .selected) + button.setImage(UIImage(named: "arrow_right_icon_01"), for: [.selected, .highlighted]) + return button + }() + + private lazy var desLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + label.numberOfLines = desTotalLineNum + return label + }() + + private lazy var lineView2: UIView = { + let view = UIView() + view.backgroundColor = self.lineColor + return view + }() + + private lazy var contentsContainerView = UIView() + + private lazy var contentsTitleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .semibold) + label.textColor = .black + label.text = "Contents" + return label + }() + + private lazy var contentsTextLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + return label + }() + + private lazy var contentsMoreButton: UIButton = { + var configuration = UIButton.Configuration.plain() + configuration.contentInsets = .zero + configuration.image = UIImage(named: "arrow_right_icon_04") + configuration.imagePadding = 0 + configuration.imagePlacement = .trailing + configuration.attributedTitle = AttributedString("Completed".localized, attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 12, weight: .regular), + .foregroundColor : UIColor.black.withAlphaComponent(0.5) + ])) + + let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + let vc = NRNovelDetailCatalogViewController() + vc.novelModel = self.model + self.viewController?.navigationController?.pushViewController(vc, animated: true) + })) + return button + }() + + private lazy var lineView3: UIView = { + let view = UIView() + view.backgroundColor = self.lineColor + return view + }() + + deinit { + scrollView.removeObserver(self, forKeyPath: "contentSize") + } + + override init(frame: CGRect) { + super.init(frame: frame) + + nr_setupUI() + } + + 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.didChangeHeight?() + } + } + +} + +extension NRNovelDetailHeaderView { + + private func nr_setupUI() { + addSubview(scrollView) + scrollView.addSubview(coverInfoView) + scrollView.addSubview(contentView) + contentView.addSubview(collectDataView) + contentView.addSubview(heatsDataView) + contentView.addSubview(wordDataView) + contentView.addSubview(rateDataView) + contentView.addSubview(dataLineView1) + contentView.addSubview(dataLineView2) + contentView.addSubview(dataLineView3) + contentView.addSubview(lineView1) + contentView.addSubview(desTitleLabel) + contentView.addSubview(desMoreButton) + contentView.addSubview(desLabel) + contentView.addSubview(lineView2) + contentView.addSubview(contentsContainerView) + contentsContainerView.addSubview(contentsTitleLabel) + contentsContainerView.addSubview(contentsTextLabel) + contentsContainerView.addSubview(contentsMoreButton) + contentView.addSubview(lineView3) + + scrollView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + coverInfoView.snp.makeConstraints { make in + make.left.centerX.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + make.height.equalTo(240) + } + + contentView.snp.makeConstraints { make in + make.left.centerX.equalToSuperview() + make.top.equalTo(coverInfoView.snp.bottom) + make.bottom.equalToSuperview() + } + + collectDataView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview().offset(16) + make.height.equalTo(50) + } + + heatsDataView.snp.makeConstraints { make in + make.width.height.centerY.equalTo(collectDataView) + make.left.equalTo(collectDataView.snp.right) + } + + wordDataView.snp.makeConstraints { make in + make.width.height.centerY.equalTo(collectDataView) + make.left.equalTo(heatsDataView.snp.right) + } + + rateDataView.snp.makeConstraints { make in + make.width.height.centerY.equalTo(wordDataView) + make.left.equalTo(wordDataView.snp.right) + make.right.equalToSuperview().offset(-16) + } + + dataLineView1.snp.makeConstraints { make in + make.width.equalTo(1) + make.height.equalTo(40) + make.centerY.equalTo(collectDataView) + make.centerX.equalTo(collectDataView.snp.right) + } + + dataLineView2.snp.makeConstraints { make in + make.width.height.centerY.equalTo(dataLineView1) + make.centerX.equalTo(heatsDataView.snp.right) + } + + dataLineView3.snp.makeConstraints { make in + make.width.height.centerY.equalTo(dataLineView1) + make.centerX.equalTo(wordDataView.snp.right) + } + + lineView1.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + make.top.equalTo(collectDataView.snp.bottom).offset(17) + make.height.equalTo(1) + } + + desTitleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerY.equalTo(desMoreButton) + } + + desMoreButton.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-6) + make.top.equalTo(lineView1.snp.bottom).offset(2) + make.width.height.equalTo(44) + } + + desLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.right.lessThanOrEqualToSuperview().offset(-16) + make.top.equalTo(lineView1.snp.bottom).offset(48) + } + + lineView2.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + make.top.equalTo(desLabel.snp.bottom).offset(12) + make.height.equalTo(1) + } + + contentsContainerView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + make.top.equalTo(lineView2.snp.bottom) + make.height.equalTo(48) + } + + contentsTitleLabel.snp.makeConstraints { make in + make.left.equalToSuperview() + make.centerY.equalToSuperview() + } + + contentsTextLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(contentsTitleLabel.snp.right).offset(12) + } + + contentsMoreButton.snp.makeConstraints { make in + make.right.equalToSuperview() + make.top.bottom.equalToSuperview() + } + + lineView3.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + make.top.equalTo(contentsContainerView.snp.bottom) + make.height.equalTo(1) + make.bottom.equalToSuperview() + } + + } + +} diff --git a/ReaderHive/Class/Novel/V/NRNovelDetailMoreLikeCell.swift b/ReaderHive/Class/Novel/V/NRNovelDetailMoreLikeCell.swift new file mode 100644 index 0000000..8014fa8 --- /dev/null +++ b/ReaderHive/Class/Novel/V/NRNovelDetailMoreLikeCell.swift @@ -0,0 +1,132 @@ +// +// NRNovelDetailMoreLikeCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit + +class NRNovelDetailMoreLikeCell: UICollectionViewCell { + + var model: NRNovelModel? { + didSet { + coverImageView.nr_setImage(model?.image_url) + titleLabel.text = model?.name + + if let text = model?.category?.first, text.count > 0 { + categoryView.isHidden = false + categoryView.text = text + } else { + categoryView.isHidden = true + } + + if model?.tag_type == .hot { + markImageView.image = UIImage(named: "hot_icon_03") + markImageView.isHidden = false + + } else if model?.tag_type == .new { + markImageView.image = UIImage(named: "new_icon_01") + markImageView.isHidden = false + } else { + markImageView.isHidden = true + } + } + } + + var didSelected: ((_ model: NRNovelModel) -> Void)? { + didSet { + if self.tapGesture.view == nil, didSelected != nil { + self.contentView.addGestureRecognizer(self.tapGesture) + } + } + } + + lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + imageView.layer.masksToBounds = true + return imageView + }() + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + label.numberOfLines = 2 + return label + }() + + lazy var categoryView: NRHomeCategoryTagView = { + let view = NRHomeCategoryTagView() + return view + }() + + + lazy var markImageView: UIImageView = { + let imageView = UIImageView() + return imageView + }() + + lazy var tapGesture: UITapGestureRecognizer = { + let tap = UITapGestureRecognizer { [weak self] _ in + guard let self = self else { return } + if let model = self.model { + self.didSelected?(model) + } + } + return tap + }() + + override init(frame: CGRect) { + super.init(frame: frame) + categoryView.text = "Satisfying" + titleLabel.text = "Vanished Without a Word" + +// self.contentView.addGestureRecognizer(UITapGestureRecognizer(actionBlock: { [weak self] _ in +// guard let self = self else { return } +// if let model = self.model { +// self.didSelected?(model) +// } +// +// })) + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension NRNovelDetailMoreLikeCell { + private func nr_setupUI() { + contentView.addSubview(coverImageView) + coverImageView.addSubview(markImageView) + contentView.addSubview(titleLabel) + contentView.addSubview(categoryView) + + + coverImageView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.bottom.equalToSuperview().offset(-68) + } + + markImageView.snp.makeConstraints { make in + make.left.top.equalToSuperview() + } + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview() + make.right.lessThanOrEqualToSuperview() + make.top.equalTo(coverImageView.snp.bottom).offset(8) + } + + categoryView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.bottom.equalToSuperview() + } + + } +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelCatalogCell.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelCatalogCell.swift new file mode 100644 index 0000000..851fa0d --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelCatalogCell.swift @@ -0,0 +1,82 @@ +// +// NRNovelCatalogCell.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/1. +// + +import UIKit +import SnapKit + +class NRNovelCatalogCell: NRTableViewCell { + + var model: NRReadChapterCatalogModel? { + didSet { +// titleLabel.text = "Chapter.##".localizedReplace(text: model?.episode ?? "") + titleLabel.text = model?.name + + lockImageView.isHidden = !(model?.is_lock ?? false) + + updateState() + } + } + + var nr_isSelected: Bool = false { + didSet { + updateState() + } + } + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + return label + }() + + private lazy var lockImageView: UIImageView = { + let imageView = UIImageView() + return imageView + }() + + 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") + } + + private func updateState() { + if nr_isSelected { + titleLabel.textColor = .F_9710_D + lockImageView.image = UIImage(named: "lock_icon_01") + } else { + titleLabel.textColor = .black.withAlphaComponent(0.5) + lockImageView.image = UIImage(named: "lock_icon_02") + } + } + +} + +extension NRNovelCatalogCell { + + private func nr_setupUI() { + contentView.addSubview(titleLabel) + contentView.addSubview(lockImageView) + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(16) + make.right.lessThanOrEqualToSuperview().offset(-70) + } + + lockImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-16) + } + } + +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadBottomView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadBottomView.swift new file mode 100644 index 0000000..bcd8f2e --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadBottomView.swift @@ -0,0 +1,294 @@ +// +// NRNovelReadBottomView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/1. +// + +import UIKit +import HWPanModal +import SnapKit + +class NRNovelReadBottomView: UIView { + + weak var viewModel: NRNovelReadViewModel? { + didSet { + self.viewModel?.addObserver(self, forKeyPath: "novelModel", context: nil) + updateProgress() + } + } + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: NRNovelReadSetManager.manager.bottomViewHeight) + } + + private var collectAnimate = false + + + lazy var contentView: UIView = { + let view = UIView() + view.backgroundColor = .white + return view + }() + + lazy var catalogButton: UIButton = { + let button = self.createButton(title: "Catalog".localized, icon: UIImage(named: "catalog_icon_01")) + button.addAction(UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.viewModel?.showCatalogView() + }), for: .touchUpInside) + return button + }() + + lazy var nightButton: UIButton = { + let button = self.createButton(title: "Night".localized, icon: UIImage(named: "night_icon_01")) + button.addAction(UIAction(handler: { [weak self] _ in + guard let self = self else { return } + let manager = NRNovelReadSetManager.manager + manager.updateIsNight(isNight: !manager.isNight) + + }), for: .touchUpInside) + return button + }() + + lazy var settingsButton: UIButton = { + let button = self.createButton(title: "Settings".localized, icon: UIImage(named: "settings_icon_01")) + button.addAction(UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.viewModel?.showSettingView() + }), for: .touchUpInside) + return button + }() + + lazy var stackView: UIStackView = { + let view = UIStackView(arrangedSubviews: [catalogButton, nightButton, settingsButton]) + view.axis = .horizontal + view.distribution = .equalSpacing + return view + }() + + + lazy var prevButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.viewModel?.skipToPrevChapter() + })) + button.titleLabel?.font = .font(ofSize: 12, weight: .regular) + button.setTitle("Prev".localized, for: .normal) + button.setTitleColor(.black, for: .normal) + button.setContentHuggingPriority(.required, for: .horizontal) + button.setContentCompressionResistancePriority(.required, for: .horizontal) + return button + }() + + lazy var nextButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.viewModel?.skipToNextChapter() + })) + button.titleLabel?.font = .font(ofSize: 12, weight: .regular) + button.setTitle("Next".localized, for: .normal) + button.setTitleColor(.black, for: .normal) + button.setContentHuggingPriority(.required, for: .horizontal) + button.setContentCompressionResistancePriority(.required, for: .horizontal) + return button + }() + + lazy var progressView: NRProgressView = { + let view = NRProgressView() + view.isUserInteractionEnabled = false + view.thumbImage = UIImage(named: "Progress-handle") + view.insets = .init(top: 6, left: 0, bottom: 6, right: 0) + view.panFinish = { [weak self] progress in + self?.progressView.progress = progress + let totalCount = self?.viewModel?.chapterCatalogList.count ?? 0 + let index = Int(floor(CGFloat(totalCount) * progress)) + self?.viewModel?.skip(chapterIndex: index, dismissMenu: false) + } + return view + }() + + lazy var collectButton: UIButton = { + var configuration = UIButton.Configuration.plain() + configuration.contentInsets = .init(top: 0, leading: 11, bottom: 0, trailing: 11) + configuration.imagePadding = 4 + + let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in + self?.handleCollectButton() + + + })) + + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + + if self.viewModel?.novelModel?.is_collect == true { + button.configuration?.background.backgroundColor = .FFEFD_4 + button.configuration?.image = UIImage(named: "done_icon_01") + button.configuration?.attributedTitle = AttributedString("In My List".localized, attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 12, weight: .medium), + .foregroundColor : UIColor.F_9710_D + ])) + } else { + button.configuration?.background.backgroundColor = .F_9710_D + button.configuration?.image = UIImage(named: "+_icon_01") + button.configuration?.attributedTitle = AttributedString("Add to My List".localized, attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 12, weight: .medium), + .foregroundColor : UIColor.white + ])) + } + } + button.layer.cornerRadius = 8 + button.layer.borderWidth = 1 + button.layer.borderColor = UIColor.black.withAlphaComponent(0.05).cgColor + + + return button + + }() + + deinit { + self.viewModel?.removeObserver(self, forKeyPath: "novelModel") + NotificationCenter.default.removeObserver(self) + } + + override init(frame: CGRect) { + super.init(frame: frame) + NotificationCenter.default.addObserver(self, selector: #selector(updateCollectStateNotification), name: NRNovelAPI.updateCollectStateNotification, object: nil) + + backgroundColor = .clear + + nr_setupUI() + } + + @MainActor 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 == "novelModel" { + updateCollectState() + } + } + + @objc private func updateCollectStateNotification(sender: Notification) { + guard !collectAnimate else { return } + guard let userInfo = sender.userInfo else { return } + guard let id = userInfo["id"] as? String else { return } + guard let state = userInfo["state"] as? Bool else { return } + guard id == self.viewModel?.novelId else { return } + + self.viewModel?.novelModel?.is_collect = state + updateCollectState() + } + + private func updateCollectState() { + collectButton.setNeedsUpdateConfiguration() + collectButton.isHidden = self.viewModel?.novelModel?.is_collect ?? false + } + + func updateProgress() { + let progress = CGFloat(viewModel?.currentPageIndexPath.section ?? 0) / CGFloat(viewModel?.chapterCatalogList.count ?? 0) + self.progressView.progress = progress + + } + +// override func backgroundConfig() -> HWBackgroundConfig { +// let config = HWBackgroundConfig() +// config.backgroundAlpha = 0 +// return config +// } + + + private func createButton(title: String, icon: UIImage?) -> UIButton { + var configuration = UIButton.Configuration.plain() + configuration.contentInsets = .zero + configuration.image = icon + configuration.imagePadding = 2 + configuration.imagePlacement = .top + configuration.attributedTitle = AttributedString(title, attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 10, weight: .regular), + .foregroundColor : UIColor.black + ])) + + let button = UIButton(configuration: configuration) + button.snp.makeConstraints { make in + make.width.equalTo(72) + } + + return button + } + + + private func handleCollectButton() { + guard let model = self.viewModel?.novelModel else { return } + guard model.is_collect == false else { return } + + guard let (catalogModel, _) = self.viewModel?.getCurrentPageData() else { return } + let isCollect = !(model.is_collect ?? false) + + Task { + if await NRNovelAPI.requestCollect(isCollect: isCollect, id: model.id ?? "", chapterId: catalogModel?.id) { + self.collectAnimate = true + self.viewModel?.novelModel?.is_collect = isCollect + self.collectButton.setNeedsUpdateConfiguration() + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in + guard let self = self else { return } + self.updateCollectState() + self.collectAnimate = false + } + } + } + } + +} + +extension NRNovelReadBottomView { + + private func nr_setupUI() { + addSubview(collectButton) + addSubview(contentView) + contentView.addSubview(stackView) + contentView.addSubview(progressView) + contentView.addSubview(prevButton) + contentView.addSubview(nextButton) + + collectButton.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalToSuperview() + make.height.equalTo(36) + } + + contentView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.height.equalTo(UIScreen.safeBottom + 105) + } + + stackView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(28) + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(48) + make.height.equalTo(56) + } + + progressView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + make.right.lessThanOrEqualTo(nextButton.snp.left).offset(0) + make.left.greaterThanOrEqualTo(prevButton.snp.right).offset(0) + } + + prevButton.snp.makeConstraints { make in + make.height.equalTo(40) + make.centerY.equalTo(progressView) + make.left.equalToSuperview().offset(20) + } + + nextButton.snp.makeConstraints { make in + make.height.equalTo(40) + make.centerY.equalTo(progressView) + make.right.equalToSuperview().offset(-20) + } + } + +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadContentBottomView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadContentBottomView.swift new file mode 100644 index 0000000..b572092 --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadContentBottomView.swift @@ -0,0 +1,146 @@ +// +// NRNovelReadContentBottomView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/27. +// + +import UIKit +import SnapKit +import YYText + +class NRNovelReadContentBottomView: UIView { + + weak var viewModel: NRNovelReadViewModel? + + ///章节目录数据 + weak var catalogModel: NRReadChapterCatalogModel? { + didSet { + updatePageLabel() + } + } + ///当前页面数据 + weak var pageModel: NRReadPageModel? { + didSet { + updatePageLabel() + } + } + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: NRNovelReadSetManager.manager.contentBottomViewHeight) + } + + private lazy var contentView: UIView = { + let view = UIView() + return view + }() + + private lazy var batteryView: NRReadBatteryView = { + let view = NRReadBatteryView() + return view + }() + + private lazy var batteryLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black + return label + }() + + private lazy var pageLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black + return label + }() + + private var timer: Timer? + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override init(frame: CGRect) { + super.init(frame: frame) + NotificationCenter.default.addObserver(self, selector: #selector(didChangedThemeNotification), name: NRNovelReadSetManager.didChangedThemeNotification, object: nil) + + timer = Timer.scheduledTimer(timeInterval: 15, target: YYTextWeakProxy(target: self), selector: #selector(didChangeTime), userInfo: nil, repeats: true) + RunLoop.current.add(timer!, forMode: .common) + + + didChangeTime() + nr_setupUI() + + didChangedThemeNotification() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// 时间变化 + @objc func didChangeTime() { + +// timeLabel.text = TimerString("HH:mm") + let level = UIDevice.current.batteryLevel + + batteryView.batteryLevel = level + + batteryLabel.text = "\(Int(level * 100))%" + } + + private func updatePageLabel() { + let page = (pageModel?.page?.intValue ?? 0) + 1 + + pageLabel.text = "\(page)/\(catalogModel?.chapterModel?.pageCount ?? 1)" + } + + @objc private func didChangedThemeNotification() { + if NRNovelReadSetManager.manager.isNight { + self.batteryView.image = UIImage(named: "battery_icon_02") + self.batteryView.batteryLevelColor = .white + self.batteryLabel.textColor = .white + self.pageLabel.textColor = .white + + } else { + self.batteryView.image = UIImage(named: "battery_icon_01") + self.batteryView.batteryLevelColor = .black + self.batteryLabel.textColor = .black + self.pageLabel.textColor = .black + } + + + } + +} + +extension NRNovelReadContentBottomView { + + private func nr_setupUI() { + addSubview(contentView) + contentView.addSubview(batteryView) + contentView.addSubview(batteryLabel) + contentView.addSubview(pageLabel) + + contentView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.bottom.equalToSuperview().offset(-UIScreen.safeBottom) + } + + batteryView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(28) + } + + batteryLabel.snp.makeConstraints { make in + make.centerY.equalTo(batteryView) + make.left.equalTo(batteryView.snp.right).offset(8) + } + + pageLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-28) + } + } + +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadContentTopView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadContentTopView.swift new file mode 100644 index 0000000..a23087d --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadContentTopView.swift @@ -0,0 +1,106 @@ +// +// NRNovelReadContentTopView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/27. +// + +import UIKit +import SnapKit + +class NRNovelReadContentTopView: UIView { + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: NRNovelReadSetManager.manager.contentTopViewHeight) + } + + weak var viewModel: NRNovelReadViewModel? + ///章节目录数据 + weak var catalogModel: NRReadChapterCatalogModel? { + didSet { + backButton.setNeedsUpdateConfiguration() + } + } + ///当前页面数据 + weak var pageModel: NRReadPageModel? + + private lazy var contentView: UIView = { + let view = UIView() + return view + }() + + private lazy var backButton: UIButton = { + var configuration = UIButton.Configuration.plain() + configuration.contentInsets = .zero + configuration.imagePadding = 12 + + let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.viewModel?.backReadPage() + })) + + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + + var textColor: UIColor = .black + + if NRNovelReadSetManager.manager.isNight { + button.configuration?.image = UIImage(named: "arrow_left_icon_04")?.withRenderingMode(.automatic).withTintColor(.white) + textColor = .white + } else { + button.configuration?.image = UIImage(named: "arrow_left_icon_04") + } + +// let text = "Chapter.##".localizedReplace(text: self.catalogModel?.episode ?? "") + let text = self.catalogModel?.name ?? "" + + + + button.configuration?.attributedTitle = AttributedString(text, attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 12, weight: .regular), + .foregroundColor : textColor + ])) + } + return button + }() + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override init(frame: CGRect) { + super.init(frame: frame) + NotificationCenter.default.addObserver(self, selector: #selector(didChangedThemeNotification), name: NRNovelReadSetManager.didChangedThemeNotification, object: nil) + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func didChangedThemeNotification() { + backButton.setNeedsUpdateConfiguration() + } + +} + +extension NRNovelReadContentTopView { + + private func nr_setupUI() { + addSubview(contentView) + contentView.addSubview(backButton) + + contentView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.safeTop) + } + + backButton.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.bottom.equalToSuperview() + } + + } + +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadFinishHeaderView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadFinishHeaderView.swift new file mode 100644 index 0000000..0e5fc02 --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadFinishHeaderView.swift @@ -0,0 +1,169 @@ +// +// NRNovelReadFinishHeaderView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import SnapKit +import YYCategories + +class NRNovelReadFinishHeaderView: UICollectionReusableView { + + var novelModel: NRNovelModel? { + didSet { + coverImageView.nr_setImage(novelModel?.image_url) + gradeView.model = novelModel + } + } + + + private lazy var coverBgView: UIView = { + let view = NRGradientView() + view.colors = [UIColor.FFC_64_A.cgColor, UIColor.FFF_7_BE.cgColor, UIColor.FFC_982.cgColor] + view.startPoint = .init(x: 0, y: 0.5) + view.endPoint = .init(x: 1, y: 0.5) + view.layer.cornerRadius = 8 + view.layer.masksToBounds = true + return view + }() + + private lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 7 + return imageView + }() + + private lazy var coverGradientLayer: CAGradientLayer = { + let layer = CAGradientLayer() + layer.colors = [UIColor.black.cgColor, UIColor.clear.cgColor] + layer.startPoint = CGPoint(x: 0.5, y: 0.4) + layer.endPoint = CGPoint(x: 0.5, y: 1.0) + layer.cornerRadius = 8 + return layer + }() + + private lazy var coverDecorateView: UIView = { + let imageView = UIImageView(image: UIImage(named: "decorate_image_01")) + return imageView + }() + + private lazy var titleLabel: NRLabel = { + let label = NRLabel() + label.font = .font(ofSize: 24, weight: .semibold).withBoldItalic() + 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 = "read_finish_title".localized + return label + }() + + private lazy var textLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + label.textAlignment = .center + label.numberOfLines = 0 + label.text = "read_finish_text".localized + return label + }() + + private lazy var gradeView: NRNovelReadStarGradeView = { + let view = NRNovelReadStarGradeView() + return view + }() + + private lazy var lineView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.05) + return view + }() + + lazy var listTitleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .semibold) + label.textColor = .black + label.text = "read_finish_list_title".localized + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + nr_setupUI() + } + + override func layoutSubviews() { + super.layoutSubviews() + coverGradientLayer.frame = coverBgView.bounds + coverBgView.layer.mask = coverGradientLayer + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRNovelReadFinishHeaderView { + + private func nr_setupUI() { + addSubview(coverBgView) + coverBgView.addSubview(coverImageView) + addSubview(coverDecorateView) + addSubview(titleLabel) + addSubview(textLabel) + addSubview(gradeView) + addSubview(lineView) + addSubview(listTitleLabel) + + coverBgView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(16) + make.width.equalTo(100) + make.height.equalTo(150) + } + + coverImageView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(2) + make.top.equalToSuperview().offset(2) + make.center.equalToSuperview() + } + + coverDecorateView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.bottom.equalTo(coverBgView).offset(12) + } + + titleLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(coverBgView.snp.bottom).offset(20) + } + + textLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.right.lessThanOrEqualToSuperview().offset(16) + make.top.equalTo(titleLabel.snp.bottom).offset(6) + } + + gradeView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + make.top.equalTo(textLabel.snp.bottom).offset(25) + } + + lineView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + make.top.equalTo(gradeView.snp.bottom).offset(24) + make.height.equalTo(1) + } + + listTitleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalTo(lineView.snp.bottom).offset(16) + } + + } + +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadGradeView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadGradeView.swift new file mode 100644 index 0000000..c84c2e7 --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadGradeView.swift @@ -0,0 +1,167 @@ +// +// NRNovelReadGradeView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/3. +// + +import UIKit +import HWPanModal +import SnapKit +import Cosmos + +class NRNovelReadGradeView: NRPanModalContentView { + + var model: NRNovelModel? { + didSet { + coverImageView.nr_setImage(model?.image_url) + nameLabel.text = model?.name + + starView.grade = model?.rate ?? 0 + starView.text = NSNumber(value: model?.rate ?? 0).toString(maximumFractionDigits: 1) + + if let text = model?.category?.first, text.count > 0 { + categoryView.isHidden = false + categoryView.text = text + } else { + categoryView.isHidden = true + } + + gradeView.model = model + } + } + + private lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + return imageView + }() + + private lazy var nameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + return label + }() + + private lazy var starView: NRStarGradeView = { + let view = NRStarGradeView() + return view + }() + + private lazy var categoryView: NRHomeCategoryTagView = { + let view = NRHomeCategoryTagView() + return view + }() + + private lazy var gradeView: NRNovelReadStarGradeView = { + let view = NRNovelReadStarGradeView() + return view + }() + + private lazy var gradeLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .medium) + label.textColor = .black + return label + }() + + private lazy var myGradeView: NRStarGradeView = { + let view = NRStarGradeView() + view.filledImage = UIImage(named: "star_icon_04") + view.emptyImage = UIImage(named: "star_icon_05") + view.updateOnTouch = false + view.fillMode = .full + view.didFinishTouching = self.didFinishTouchingGrade + return view + }() + + private lazy var gradeFinishView: UIView = { + var configuration = UIButton.Configuration.plain() + configuration.imagePadding = 8 + configuration.image = UIImage(named: "done_icon_02") + configuration.attributedTitle = AttributedString("Rating Submitted!".localized, attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 12, weight: .medium), + .foregroundColor : UIColor.black + ])) + + + let view = UIButton(configuration: configuration) + view.isUserInteractionEnabled = false + view.isHidden = true + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + contentHeight = UIScreen.safeBottom + 182 + + backgroundColor = .white + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func backgroundConfig() -> HWBackgroundConfig { + let config = HWBackgroundConfig() + config.backgroundAlpha = 0 + return config + } + + override func hw_gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } + + private func didFinishTouchingGrade(_ grade: Double) { + guard let id = self.model?.id else { return } + Task { + guard await NRNovelAPI.requestRateScore(id, stars: CGFloat(grade)) else { return} + self.model?.self_rate = grade + myGradeView.isHidden = true + gradeLabel.isHidden = true + gradeFinishView.isHidden = false + } + } +} + +extension NRNovelReadGradeView { + private func nr_setupUI() { + addSubview(coverImageView) + addSubview(nameLabel) + addSubview(starView) + addSubview(categoryView) + addSubview(gradeView) + + coverImageView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview().offset(16) + make.width.equalTo(60) + make.height.equalTo(90) + } + + nameLabel.snp.makeConstraints { make in + make.top.equalTo(coverImageView).offset(8) + make.left.equalTo(coverImageView.snp.right).offset(12) + } + + starView.snp.makeConstraints { make in + make.left.equalTo(nameLabel) + make.top.equalTo(coverImageView).offset(34) + } + + categoryView.snp.makeConstraints { make in + make.left.equalTo(nameLabel) + make.bottom.equalTo(coverImageView).offset(-8) + } + + gradeView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.right.equalToSuperview().offset(-16) + make.top.equalTo(coverImageView.snp.bottom).offset(20) + } + + } +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadMoreView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadMoreView.swift new file mode 100644 index 0000000..a847130 --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadMoreView.swift @@ -0,0 +1,160 @@ +// +// NRNovelReadMoreView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/28. +// + +import UIKit +import HWPanModal +import SnapKit + +class NRNovelReadMoreView: NRPanModalContentView { + + weak var viewModel: NRNovelReadViewModel? { + didSet { + let progress = CGFloat(viewModel?.currentPageIndexPath.section ?? 0) / CGFloat(viewModel?.chapterCatalogList.count ?? 0) + self.progressView.progress = progress + } + } + + lazy var catalogButton: UIButton = { + let button = self.createButton(title: "Catalog".localized, icon: UIImage(named: "catalog_icon_01")) + return button + }() + + lazy var nightButton: UIButton = { + let button = self.createButton(title: "Night".localized, icon: UIImage(named: "night_icon_01")) + return button + }() + + lazy var settingsButton: UIButton = { + let button = self.createButton(title: "Settings".localized, icon: UIImage(named: "settings_icon_01")) + button.addAction(UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.dismiss(animated: true) { } + + self.viewModel?.showSettingView() + }), for: .touchUpInside) + return button + }() + + lazy var stackView: UIStackView = { + let view = UIStackView(arrangedSubviews: [catalogButton, nightButton, settingsButton]) + view.axis = .horizontal + view.distribution = .equalSpacing + return view + }() + + lazy var prevLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black + label.text = "Prev".localized + label.setContentHuggingPriority(.required, for: .horizontal) + label.setContentCompressionResistancePriority(.required, for: .horizontal) + return label + }() + + lazy var nextLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black + label.text = "Next".localized + label.setContentHuggingPriority(.required, for: .horizontal) + label.setContentCompressionResistancePriority(.required, for: .horizontal) + return label + }() + + lazy var progressView: NRProgressView = { + let view = NRProgressView() + view.thumbImage = UIImage(named: "Progress-handle") + view.insets = .init(top: 6, left: 0, bottom: 6, right: 0) + view.panFinish = { [weak self] progress in + self?.progressView.progress = progress + let totalCount = self?.viewModel?.chapterCatalogList.count ?? 0 + let index = Int(floor(CGFloat(totalCount) * progress)) + self?.viewModel?.skip(chapterIndex: index) + } + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + contentHeight = UIScreen.safeBottom + 105 + + backgroundColor = .white + + nr_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func backgroundConfig() -> HWBackgroundConfig { + let config = HWBackgroundConfig() + config.backgroundAlpha = 0 + return config + } + + override func cornerRadius() -> CGFloat { + return 0 + } + + private func createButton(title: String, icon: UIImage?) -> UIButton { + var configuration = UIButton.Configuration.plain() + configuration.contentInsets = .zero + configuration.image = icon + configuration.imagePadding = 2 + configuration.imagePlacement = .top + configuration.attributedTitle = AttributedString(title, attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 10, weight: .regular), + .foregroundColor : UIColor.black + ])) + + let button = UIButton(configuration: configuration) + button.snp.makeConstraints { make in + make.width.equalTo(72) + } + + return button + } + +} + +extension NRNovelReadMoreView { + + private func nr_setupUI() { + addSubview(stackView) + addSubview(progressView) + addSubview(prevLabel) + addSubview(nextLabel) + + stackView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(28) + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(48) + make.height.equalTo(56) + } + + progressView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(8) + make.centerX.equalToSuperview() + make.right.lessThanOrEqualTo(nextLabel.snp.left).offset(0) + make.left.greaterThanOrEqualTo(prevLabel.snp.right).offset(0) + } + + prevLabel.snp.makeConstraints { make in + make.centerY.equalTo(progressView) + make.left.equalToSuperview().offset(20) + } + + nextLabel.snp.makeConstraints { make in + make.centerY.equalTo(progressView) + make.right.equalToSuperview().offset(-20) + } + } + +} + diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadSettingItemView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadSettingItemView.swift new file mode 100644 index 0000000..77c0861 --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadSettingItemView.swift @@ -0,0 +1,49 @@ +// +// NRNovelReadSettingItemView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/29. +// + +import UIKit +import SnapKit + +class NRNovelReadSettingItemView: UIView { + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .medium) + label.textColor = .black + label.numberOfLines = 0 + return label + }() + + lazy var contentView: UIView = { + let view = UIView() + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + addSubview(titleLabel) + addSubview(contentView) + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(16) + make.width.equalTo(84) + } + + contentView.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.left.equalTo(titleLabel.snp.right).offset(12) + make.right.equalToSuperview().offset(-16) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadSettingView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadSettingView.swift new file mode 100644 index 0000000..a5c8fd6 --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadSettingView.swift @@ -0,0 +1,81 @@ +// +// NRNovelReadSettingView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/29. +// + +import UIKit +import HWPanModal +import SnapKit + +class NRNovelReadSettingView: NRPanModalContentView { + + + lazy var brightnessView: NRReadSettingBrightnessView = { + let view = NRReadSettingBrightnessView() + return view + }() + + lazy var themeView: NRReadSettingThemeView = { + let view = NRReadSettingThemeView() + return view + }() + + lazy var lineSpacingView: NRReadSettingSpacingView = { + let view = NRReadSettingSpacingView(type: .line) + view.titleLabel.text = "Line Spacing".localized + return view + }() + + lazy var paragraphSpacingView: NRReadSettingSpacingView = { + let view = NRReadSettingSpacingView(type: .paragraph) + view.titleLabel.text = "Paragraph Spacing".localized + return view + }() + + lazy var fontView: NRReadSettingFontView = { + let view = NRReadSettingFontView() + return view + }() + + lazy var stackView: UIStackView = { + let view = UIStackView(arrangedSubviews: [brightnessView, fontView, themeView, lineSpacingView, paragraphSpacingView]) + view.axis = .vertical + view.spacing = 20 + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + contentHeight = UIScreen.safeBottom + 260 + 16 + + backgroundColor = .white + + nr_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func backgroundConfig() -> HWBackgroundConfig { + let config = HWBackgroundConfig() + config.backgroundAlpha = 0 + return config + } + +} + +extension NRNovelReadSettingView { + + private func nr_setupUI() { + addSubview(stackView) + + stackView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().offset(16) + } + } + +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadStarGradeView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadStarGradeView.swift new file mode 100644 index 0000000..8ae7fea --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadStarGradeView.swift @@ -0,0 +1,117 @@ +// +// NRNovelReadStarGradeView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import SnapKit +import Cosmos + +class NRNovelReadStarGradeView: UIView { + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: 48) + } + + + var model: NRNovelModel? { + didSet { + let self_rate = model?.self_rate ?? 0 + if self_rate < 0 { + label.text = "Enjoying this book?".localized + gradeView.grade = 0 + gradeView.updateOnTouch = true + gradeView.fillMode = .full + } else { + label.text = "My Rate:##".localizedReplace(text: NSNumber(value: self_rate).toString()) + gradeView.grade = self_rate + gradeView.updateOnTouch = false + gradeView.fillMode = .precise + } + } + } + + private lazy var label: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .medium) + label.textColor = .black + return label + }() + + private lazy var gradeView: NRStarGradeView = { + let view = NRStarGradeView() + view.filledImage = UIImage(named: "star_icon_04") + view.emptyImage = UIImage(named: "star_icon_05") + view.updateOnTouch = false + view.fillMode = .full + view.didFinishTouching = self.didFinishTouchingGrade + return view + }() + + private lazy var finishView: UIView = { + var configuration = UIButton.Configuration.plain() + configuration.imagePadding = 8 + configuration.image = UIImage(named: "done_icon_02") + configuration.attributedTitle = AttributedString("Rating Submitted!".localized, attributes: AttributeContainer([ + .font : UIFont.font(ofSize: 12, weight: .medium), + .foregroundColor : UIColor.black + ])) + + + let view = UIButton(configuration: configuration) + view.isUserInteractionEnabled = false + view.isHidden = true + return view + }() + + + override init(frame: CGRect) { + super.init(frame: frame) + layer.cornerRadius = 8 + layer.masksToBounds = true + backgroundColor = .FFEFD_4 + + nr_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func didFinishTouchingGrade(_ grade: Double) { + guard let id = self.model?.id else { return } + Task { + guard await NRNovelAPI.requestRateScore(id, stars: CGFloat(grade)) else { return} + self.model?.self_rate = grade + gradeView.isHidden = true + label.isHidden = true + finishView.isHidden = false + } + } +} + +extension NRNovelReadStarGradeView { + + private func nr_setupUI() { + addSubview(label) + addSubview(gradeView) + addSubview(finishView) + + label.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(12) + } + + gradeView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-12) + } + + finishView.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } + +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadTopView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadTopView.swift new file mode 100644 index 0000000..408fddd --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadTopView.swift @@ -0,0 +1,165 @@ +// +// NRNovelReadTopView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/27. +// + +import UIKit +import SnapKit + +class NRNovelReadTopView: UIView { + + weak var viewModel: NRNovelReadViewModel? { + didSet { + self.viewModel?.addObserver(self, forKeyPath: "novelModel", context: nil) +// collectButton.setNeedsUpdateConfiguration() + } + } + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: NRNovelReadSetManager.manager.topViewHeight) + } + + private lazy var contentView: UIView = { + let view = UIView() + return view + }() + + + private lazy var backButton: UIButton = { + var configuration = UIButton.Configuration.plain() + configuration.contentInsets = .zero + configuration.image = UIImage(named: "arrow_left_icon_03") + configuration.imagePadding = 12 + + let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.viewModel?.backReadPage() + })) + + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + + var chapter = "1" + if let (catalogModel, _) = self.viewModel?.getCurrentPageData() { + chapter = catalogModel?.name ?? "1" + } + + button.configuration?.attributedTitle = /*AttributedString("Chapter.##".localizedReplace(text: chapter), attributes:*/ + AttributedString(chapter, attributes: + AttributeContainer([ + .font : UIFont.font(ofSize: 12, weight: .regular), + .foregroundColor : UIColor.black + ])) + } + return button + }() + + private lazy var moreButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.viewModel?.showMoreView() + })) + button.setImage(UIImage(named: "more_icon_01"), for: .normal) + return button + }() + + private lazy var collectButton: UIButton = { + var configuration = UIButton.Configuration.plain() + configuration.contentInsets = .zero + + let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + guard let model = self.viewModel?.novelModel else { return } + guard let (catalogModel, _) = self.viewModel?.getCurrentPageData() else { return } + + let isCollect = !(model.is_collect ?? false) + + Task { + await NRNovelAPI.requestCollect(isCollect: isCollect, id: model.id ?? "", chapterId: catalogModel?.id) + } + })) + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + + if self.viewModel?.novelModel?.is_collect ?? false { + button.configuration?.image = UIImage(named: "collect_icon_03_selected") + } else { + button.configuration?.image = UIImage(named: "collect_icon_03") + } + } + + return button + }() + + deinit { +// NotificationCenter.default.removeObserver(self) + self.viewModel?.removeObserver(self, forKeyPath: "novelModel") + } + + override init(frame: CGRect) { + super.init(frame: frame) + self.backgroundColor = .white +// NotificationCenter.default.addObserver(self, selector: #selector(updateCollectStateNotification), name: NRNovelAPI.updateCollectStateNotification, object: nil) + + nr_setupUI() + } + + 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 == "novelModel" { +// collectButton.setNeedsUpdateConfiguration() + } + } + + @objc private func updateCollectStateNotification(sender: Notification) { + guard let userInfo = sender.userInfo else { return } + guard let id = userInfo["id"] as? String else { return } + guard let state = userInfo["state"] as? Bool else { return } + guard id == self.viewModel?.novelId else { return } + + self.viewModel?.novelModel?.is_collect = state + + collectButton.setNeedsUpdateConfiguration() + } + + func reloadData() { + backButton.setNeedsUpdateConfiguration() + } +} + +extension NRNovelReadTopView { + + private func nr_setupUI() { + addSubview(contentView) + contentView.addSubview(backButton) + contentView.addSubview(moreButton) +// contentView.addSubview(collectButton) + + contentView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.safeTop) + } + + backButton.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.bottom.equalToSuperview() + } + + moreButton.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.right.equalToSuperview().offset(-16) + } + +// collectButton.snp.makeConstraints { make in +// make.top.bottom.equalToSuperview() +// make.right.equalTo(moreButton.snp.left).offset(-12) +// } + } + + +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadView.swift new file mode 100644 index 0000000..fb299df --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadView.swift @@ -0,0 +1,71 @@ +// +// NRNovelReadView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/28. +// + +import UIKit + +class NRNovelReadView: UIView { + + /// 当前页模型(使用contentSize绘制) + var pageModel: NRReadPageModel! { + didSet{ +// frameRef = NRCoreText.GetFrameRef(attrString: pageModel.showContent, rect: CGRect(origin: CGPoint.zero, size: pageModel.contentSize)) + + let manter = NRNovelReadSetManager.manager + frameRef = NRCoreText.GetFrameRef(attrString: content, rect: CGRect(origin: CGPoint.zero, size: manter.readRect.size)) + } + } + + /// 当前页内容(使用固定范围绘制) + var content:NSAttributedString! { + + didSet{ + let manager = NRNovelReadSetManager.manager + frameRef = NRCoreText.GetFrameRef(attrString: content, rect: CGRect(origin: .init(x: manager.readRect.minX, y: 0), size: manager.readRect.size)) + } + } + + /// CTFrame + var frameRef:CTFrame? { + + didSet{ + + if frameRef != nil { setNeedsDisplay() } + } + } + + override init(frame: CGRect) { + + super.init(frame: frame) + + // 正常使用 + backgroundColor = UIColor.clear + + // 可以修改为随机颜色便于调试范围 +// backgroundColor = DZM_COLOR_ARC + } + + /// 绘制 + override func draw(_ rect: CGRect) { + + if (frameRef == nil) {return} + + let ctx = UIGraphicsGetCurrentContext() + + ctx?.textMatrix = CGAffineTransform.identity + + ctx?.translateBy(x: 0, y: bounds.size.height); + + ctx?.scaleBy(x: 1.0, y: -1.0); + + CTFrameDraw(frameRef!, ctx!); + } + + required init?(coder aDecoder: NSCoder) { + + fatalError("init(coder:) has not been implemented") + } +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReaderCatalogView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReaderCatalogView.swift new file mode 100644 index 0000000..0f00397 --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReaderCatalogView.swift @@ -0,0 +1,272 @@ +// +// NRNovelReaderCatalogView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/1. +// + +import UIKit +import SnapKit + +class NRNovelReaderCatalogView: UIView { + + var didSelected: ((_ index: Int) -> Void)? + + var novelModel: NRNovelModel? { + didSet { + coverImageView.nr_setImage(novelModel?.image_url) + nameLabel.text = novelModel?.name + + totalChaptersLabel.text = "## Chapters".localizedReplace(text: "\(novelModel?.episode_total ?? 0)") + } + } + + var catalogDataArr: [NRReadChapterCatalogModel] = [] { + didSet { + updateProgress() + reloadData() + } + } + + var currentCatalogModel: NRReadChapterCatalogModel? { + didSet { + updateProgress() + reloadData() + } + } + + let contentWidth = UIScreen.width - 95 + + ///是否需要滚动到当前章节 + private var isScrollToCurrent = true + + private lazy var contentView: UIView = { + let view = UIView() + view.backgroundColor = .white + return view + }() + + private lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + return imageView + }() + + private lazy var nameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + return label + }() + + private lazy var totalChaptersLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black.withAlphaComponent(0.25) + return label + }() + + private lazy var redProgressLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .medium) + label.textColor = .F_9710_D + return label + }() + + private lazy var lineView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.15) + return view + }() + + private lazy var tableView: NRTableView = { + let tableView = NRTableView(frame: .zero, style: .plain) + tableView.delegate = self + tableView.dataSource = self + tableView.separatorStyle = .none + tableView.rowHeight = 44 + tableView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.safeBottom, right: 0) + tableView.register(NRNovelCatalogCell.self, forCellReuseIdentifier: "cell") + return tableView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + backgroundColor = .black.withAlphaComponent(0.75) + + nr_setupUI() + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + let touch = touches.first + guard let point = touch?.location(in: self.contentView) else { return } + + if !self.contentView.bounds.contains(point) { + self.dismiss() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func show() { + let view = NRTool.keyWindow + + view?.addSubview(self) + + self.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + UIView.animate(withDuration: 0.2) { [weak self] in + guard let self = self else { return } + self.contentView.transform = CGAffineTransform(translationX: self.contentWidth, y: 0) + } + } + + func dismiss() { + UIView.animate(withDuration: 0.2) { [weak self] in + guard let self = self else { return } + self.contentView.transform = CGAffineTransform.identity + } completion: { [weak self] _ in + self?.removeFromSuperview() + } + } + + + private func getCurrentIndex() -> Int { + var currentIndex = 0 + for (i, catalogModel) in catalogDataArr.enumerated() { + if catalogModel.id == self.currentCatalogModel?.id { + currentIndex = i + break + } + } + return currentIndex + } + + private func updateProgress() { + guard catalogDataArr.count > 0, self.currentCatalogModel != nil else { + self.redProgressLabel.text = "## read".localizedReplace(text: "0%") + return + } + let currentIndex = getCurrentIndex() + + let progress = NSNumber(value: CGFloat(currentIndex + 1) / CGFloat(catalogDataArr.count) * 100).toString(maximumFractionDigits: 0) + + self.redProgressLabel.text = "## read".localizedReplace(text: "\(progress)%") + + } + + private func reloadData() { + + UIView.performWithoutAnimation { + self.tableView.reloadData() + } + + self.tableView.performBatchUpdates(nil) { [weak self] _ in + guard let self = self else { return } + + if self.isScrollToCurrent { + let row = self.getCurrentIndex() + + self.tableView.scrollToRow(at: IndexPath(row: row, section: 0), at: .top, animated: false) + + } + } + + + } + +} + +extension NRNovelReaderCatalogView { + + private func nr_setupUI() { + addSubview(contentView) + contentView.addSubview(coverImageView) + contentView.addSubview(nameLabel) + contentView.addSubview(totalChaptersLabel) + contentView.addSubview(redProgressLabel) + contentView.addSubview(lineView) + contentView.addSubview(tableView) + + contentView.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.width.equalTo(contentWidth) + make.right.equalTo(self.snp.left) + } + + coverImageView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview().offset(UIScreen.safeTop + 12) + make.width.equalTo(60) + make.height.equalTo(90) + } + + nameLabel.snp.makeConstraints { make in + make.top.equalTo(coverImageView) + make.left.equalTo(coverImageView.snp.right).offset(12) + make.right.lessThanOrEqualToSuperview().offset(-12) + } + + totalChaptersLabel.snp.makeConstraints { make in + make.left.equalTo(nameLabel) + make.top.equalTo(nameLabel.snp.bottom).offset(8) + } + + redProgressLabel.snp.makeConstraints { make in + make.bottom.equalTo(coverImageView) + make.left.equalTo(nameLabel) + } + + lineView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.right.equalToSuperview().offset(-16) + make.top.equalTo(coverImageView.snp.bottom).offset(16) + make.height.equalTo(1) + } + + tableView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.bottom.equalToSuperview() + make.top.equalTo(lineView.snp.bottom).offset(6) + } + } + +} + +//MARK: UITableViewDelegate UITableViewDataSource +extension NRNovelReaderCatalogView: UITableViewDelegate, UITableViewDataSource { + + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let model = self.catalogDataArr[indexPath.row] + + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! NRNovelCatalogCell + cell.model = model + cell.nr_isSelected = model.id == self.currentCatalogModel?.id + return cell + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.catalogDataArr.count + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let lastIndex = indexPath.row - 1 + var lastModel: NRReadChapterCatalogModel? + if lastIndex >= 0 { + lastModel = self.catalogDataArr[lastIndex] + } + ///上一集加锁状态禁止点击 + if lastModel?.is_lock == true { return } + + self.didSelected?(indexPath.row) + self.dismiss() + } + +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRReadBatteryView.swift b/ReaderHive/Class/Novel/V/Reader/NRReadBatteryView.swift new file mode 100644 index 0000000..619f8ab --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRReadBatteryView.swift @@ -0,0 +1,104 @@ +// +// NRReadBatteryView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/27. +// + +import UIKit +import SnapKit + +class NRReadBatteryView: UIImageView { + + /// 颜色 + var batteryLevelColor: UIColor? { + didSet { + levelView.backgroundColor = batteryLevelColor + } + } + + /// BatteryLevel + var batteryLevel:Float = 0 { + + didSet{ setNeedsLayout() } + } + + private lazy var levelView: UIView = { + let view = UIView() + view.layer.cornerRadius = 1.33 + view.layer.masksToBounds = true + return view + }() + + override init(image: UIImage?) { + super.init(image: image) + _init() + } + + override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + override init(image: UIImage?, highlightedImage: UIImage?) { + super.init(image: image, highlightedImage: highlightedImage) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + + override func awakeFromNib() { + super.awakeFromNib() + _init() + } + + private func _init() { + self.image = UIImage(named: "battery_icon_01") + + self.batteryLevelColor = .black + + nr_setupUI() + } + + override func layoutSubviews() { + super.layoutSubviews() + + // 判断电量 + var tempBatteryLevel = batteryLevel + + if batteryLevel < 0 { + + tempBatteryLevel = 0 + + }else if batteryLevel > 1 { + + tempBatteryLevel = 1 + + }else{} + + let width = 18 * tempBatteryLevel + + levelView.snp.updateConstraints { make in + make.width.equalTo(width) + } + } + +} + +extension NRReadBatteryView { + + private func nr_setupUI() { + addSubview(levelView) + + levelView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.height.equalTo(7.3) + make.left.equalToSuperview().offset(2) + make.width.equalTo(0) + } + } + +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRReadSettingBrightnessView.swift b/ReaderHive/Class/Novel/V/Reader/NRReadSettingBrightnessView.swift new file mode 100644 index 0000000..eb59e01 --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRReadSettingBrightnessView.swift @@ -0,0 +1,49 @@ +// +// NRReadSettingBrightnessView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/29. +// + +import UIKit +import SnapKit + +class NRReadSettingBrightnessView: NRNovelReadSettingItemView { + + + lazy var progressView: NRProgressView = { + let view = NRProgressView() + view.thumbImage = UIImage(named: "Progress-handle") + view.insets = .init(top: 6, left: 0, bottom: 6, right: 0) + view.panChange = { progress in + UIScreen.main.brightness = progress + } + view.panFinish = { [weak self] progress in + self?.progressView.progress = progress + UIScreen.main.brightness = progress + } + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.titleLabel.text = "Brightness".localized + + let imageSize = progressView.thumbImage?.size ?? .zero + + contentView.addSubview(progressView) + + progressView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(-imageSize.width / 2) + make.right.equalToSuperview().offset(imageSize.width / 2) + make.top.bottom.equalToSuperview() + } + + progressView.progress = UIScreen.main.brightness + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRReadSettingFontView.swift b/ReaderHive/Class/Novel/V/Reader/NRReadSettingFontView.swift new file mode 100644 index 0000000..d8d188c --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRReadSettingFontView.swift @@ -0,0 +1,125 @@ +// +// NRReadSettingFontView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/1. +// + +import UIKit +import YYCategories +import SnapKit + +class NRReadSettingFontView: NRNovelReadSettingItemView { + + + + private lazy var bgView: UIView = { + let view = UIView() + view.backgroundColor = .F_2_EFEE + view.layer.cornerRadius = 8 + view.layer.masksToBounds = true + return view + }() + + private lazy var minishButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + let size = NRNovelReadSetManager.manager.readSet.fontSize + NRNovelReadSetManager.manager.updateFontSize(size: size - 2) + self.reloadData() + })) + button.setTitle("A-", for: .normal) + button.titleLabel?.font = .font(ofSize: 16, weight: .regular) + return button + }() + + private lazy var addButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + let size = NRNovelReadSetManager.manager.readSet.fontSize + NRNovelReadSetManager.manager.updateFontSize(size: size + 2) + self.reloadData() + })) + button.setTitle("A+", for: .normal) + button.titleLabel?.font = .font(ofSize: 16, weight: .regular) + return button + }() + + private lazy var lineView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.15) + return view + }() + + + override init(frame: CGRect) { + super.init(frame: frame) + self.titleLabel.text = "Font Size".localized + + nr_setupUI() + + reloadData() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func reloadData() { + let size = NRNovelReadSetManager.manager.readSet.fontSize + let maxSize = NRNovelReadSetManager.manager.maxFontSize + let miniSize = NRNovelReadSetManager.manager.miniFontSize + + if size <= miniSize { + self.minishButton.isEnabled = false + self.minishButton.setTitleColor(.black.withAlphaComponent(0.25), for: .normal) + } else { + self.minishButton.isEnabled = true + self.minishButton.setTitleColor(.black, for: .normal) + } + + if size >= maxSize { + self.addButton.isEnabled = false + self.addButton.setTitleColor(.black.withAlphaComponent(0.25), for: .normal) + } else { + self.addButton.isEnabled = true + self.addButton.setTitleColor(.black, for: .normal) + } + + } + +} + +extension NRReadSettingFontView { + + private func nr_setupUI() { + contentView.addSubview(bgView) + bgView.addSubview(minishButton) + bgView.addSubview(addButton) + bgView.addSubview(lineView) + + bgView.snp.makeConstraints { make in + make.top.bottom.left.equalToSuperview() + make.width.equalTo(138) + make.height.equalTo(36) + } + + minishButton.snp.makeConstraints { make in + make.left.top.bottom.equalToSuperview() + } + + addButton.snp.makeConstraints { make in + make.right.top.bottom.equalToSuperview() + make.left.equalTo(minishButton.snp.right) + make.width.equalTo(minishButton) + } + + lineView.snp.makeConstraints { make in + make.center.equalToSuperview() + make.width.equalTo(1) + make.height.equalTo(22) + } + + } + +} diff --git a/ReaderHive/Class/Novel/V/Reader/NRReadSettingSpacingView.swift b/ReaderHive/Class/Novel/V/Reader/NRReadSettingSpacingView.swift new file mode 100644 index 0000000..4825533 --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRReadSettingSpacingView.swift @@ -0,0 +1,110 @@ +// +// NRReadSettingSpacingView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/1. +// + +import UIKit +import SnapKit + +class NRReadSettingSpacingView: NRNovelReadSettingItemView { + enum ViewType { + case line + case paragraph + } + + private var viewType: ViewType = .line + + private let spacingTypeArr: [NRNovelReadSet.SpacingType] = [.small, .standard, .large] + private var buttons: [UIButton] = [] + + private lazy var stackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: []) + stackView.axis = .horizontal + stackView.spacing = 8 + stackView.distribution = .fillEqually + return stackView + }() + + init(type: ViewType) { + super.init(frame: .zero) + self.viewType = type + + nr_setupUI() + + updateSelection() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} +extension NRReadSettingSpacingView { + + private func nr_setupUI() { + contentView.addSubview(stackView) + + stackView.snp.makeConstraints { make in +// make.top.bottom.left.equalToSuperview() + make.top.equalToSuperview() + make.centerY.equalToSuperview() + make.left.equalToSuperview() + make.right.lessThanOrEqualToSuperview() + make.height.equalTo(36) + } + + for type in spacingTypeArr { + let button = UIButton(type: .custom) + button.layer.cornerRadius = 8 + button.layer.masksToBounds = true + button.layer.borderWidth = 1 + button.tag = type.rawValue + + button.setTitle(type.title, for: .normal) + button.titleLabel?.font = .font(ofSize: 10, weight: .regular) + button.addTarget(self, action: #selector(typeButtonTapped(_:)), for: .touchUpInside) + + stackView.addArrangedSubview(button) + buttons.append(button) + + button.snp.makeConstraints { make in + make.width.greaterThanOrEqualTo(72) + } + } + } + + @objc private func typeButtonTapped(_ sender: UIButton) { + guard let type = NRNovelReadSet.SpacingType(rawValue: sender.tag) else { return } + if viewType == .line { + NRNovelReadSetManager.manager.updateLineSpacing(type: type) + } else { + NRNovelReadSetManager.manager.updateParagraphSpacing(type: type) + } + updateSelection() + } + + private func updateSelection() { + let currentType: Int + if viewType == .line { + currentType = NRNovelReadSetManager.manager.readSet.lineSpacingType.rawValue + } else { + currentType = NRNovelReadSetManager.manager.readSet.paragraphSpacingType.rawValue + } + + for button in buttons { + if button.tag == currentType { + button.layer.borderColor = UIColor.clear.cgColor + button.backgroundColor = .F_9710_D + button.setTitleColor(.white, for: .normal) + } else { + button.layer.borderColor = UIColor.black.withAlphaComponent(0.25).cgColor + button.backgroundColor = .clear + button.setTitleColor(.black.withAlphaComponent(0.25), for: .normal) + } + } + } + +} + diff --git a/ReaderHive/Class/Novel/V/Reader/NRReadSettingThemeView.swift b/ReaderHive/Class/Novel/V/Reader/NRReadSettingThemeView.swift new file mode 100644 index 0000000..52881ed --- /dev/null +++ b/ReaderHive/Class/Novel/V/Reader/NRReadSettingThemeView.swift @@ -0,0 +1,95 @@ +// +// NRReadSettingThemeView.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/29. +// + +import UIKit +import SnapKit + +class NRReadSettingThemeView: NRNovelReadSettingItemView { + + + private let themes: [NRReadThemeType] = [.theme1, .theme2, .theme3, .theme4] + private var buttons: [UIButton] = [] + + private lazy var stackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: []) + stackView.axis = .horizontal + stackView.spacing = 8 + stackView.distribution = .fillEqually + return stackView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.titleLabel.text = "Theme".localized + nr_setupUI() + updateSelection() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRReadSettingThemeView { + + private func nr_setupUI() { + contentView.addSubview(stackView) + + stackView.snp.makeConstraints { make in + make.top.bottom.left.equalToSuperview() + make.right.lessThanOrEqualToSuperview() + make.height.equalTo(36) + } + + for themeType in themes { + let button = UIButton(type: .custom) + button.layer.cornerRadius = 8 + button.layer.masksToBounds = true + button.layer.borderWidth = 1 + button.tag = themeType.rawValue + + let theme = NRReadTheme.theme(for: themeType) + button.backgroundColor = theme.backgroundColor + button.setTitle("T", for: .normal) + button.setTitleColor(theme.textColor, for: .normal) + button.titleLabel?.font = .font(ofSize: 14, weight: .medium) + button.addTarget(self, action: #selector(themeButtonTapped(_:)), for: .touchUpInside) + + stackView.addArrangedSubview(button) + buttons.append(button) + + button.snp.makeConstraints { make in + make.width.greaterThanOrEqualTo(48) + } + } + } + + @objc private func themeButtonTapped(_ sender: UIButton) { + guard let type = NRReadThemeType(rawValue: sender.tag) else { return } + NRNovelReadSetManager.manager.updateTheme(type: type) + updateSelection() + } + + private func updateSelection() { + let currentType = NRNovelReadSetManager.manager.readSet.theme.rawValue + for button in buttons { + if button.tag == currentType { + button.layer.borderColor = UIColor.F_9710_D.cgColor + button.layer.borderWidth = 3 + button.setTitle(nil, for: .normal) + button.setImage(UIImage(named: "done_icon_03"), for: .normal) + } else { + button.layer.borderColor = UIColor.black.withAlphaComponent(0.1).cgColor + button.layer.borderWidth = 1 + button.setTitle("T", for: .normal) + button.setImage(nil, for: .normal) + } + } + } + +} diff --git a/ReaderHive/Class/Novel/VC/NRNovelDetailCatalogViewController.swift b/ReaderHive/Class/Novel/VC/NRNovelDetailCatalogViewController.swift new file mode 100644 index 0000000..95fe6ed --- /dev/null +++ b/ReaderHive/Class/Novel/VC/NRNovelDetailCatalogViewController.swift @@ -0,0 +1,178 @@ +// +// NRNovelDetailCatalogViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/4. +// + +import UIKit +import SnapKit + +class NRNovelDetailCatalogViewController: NRViewController { + + var novelModel: NRNovelModel? { + didSet { + coverImageView.nr_setImage(novelModel?.image_url) + nameLabel.text = novelModel?.name + + totalChaptersLabel.text = "## Chapters".localizedReplace(text: "\(novelModel?.episode_total ?? 0)") + + Task { + await requestDataArr() + } + } + } + + lazy var dataArr: [NRReadChapterCatalogModel] = [] + + + private lazy var lineView: UIView = { + let view = UIView() + view.backgroundColor = .black.withAlphaComponent(0.15) + return view + }() + + private lazy var coverImageView: NRImageView = { + let imageView = NRImageView() + imageView.layer.cornerRadius = 4 + return imageView + }() + + private lazy var nameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .black + return label + }() + + private lazy var totalChaptersLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .black.withAlphaComponent(0.25) + return label + }() + + private lazy var tableView: NRTableView = { + let tableView = NRTableView(frame: .zero, style: .plain) + tableView.delegate = self + tableView.dataSource = self + tableView.separatorStyle = .none + tableView.rowHeight = 44 + tableView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.safeBottom, right: 0) + tableView.register(NRNovelCatalogCell.self, forCellReuseIdentifier: "cell") + return tableView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.backgroundImageView.isHidden = true + self.title = "Catalog".localized + 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 NRNovelDetailCatalogViewController { + + private func nr_setupUI() { + view.addSubview(lineView) + view.addSubview(coverImageView) + view.addSubview(nameLabel) + view.addSubview(totalChaptersLabel) + view.addSubview(tableView) + + lineView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + make.height.equalTo(1) + } + + coverImageView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalTo(lineView.snp.bottom).offset(16) + make.width.equalTo(60) + make.height.equalTo(90) + } + + nameLabel.snp.makeConstraints { make in + make.top.equalTo(coverImageView) + make.left.equalTo(coverImageView.snp.right).offset(12) + make.right.lessThanOrEqualToSuperview().offset(-16) + } + + totalChaptersLabel.snp.makeConstraints { make in + make.left.equalTo(nameLabel) + make.top.equalTo(nameLabel.snp.bottom).offset(8) + } + + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(coverImageView.snp.bottom).offset(16) + } + } + +} + +//MARK: UITableViewDelegate UITableViewDataSource +extension NRNovelDetailCatalogViewController: 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! NRNovelCatalogCell + cell.model = model + return cell + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.dataArr.count + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let id = self.novelModel?.id else { return } + let model = dataArr[indexPath.row] + + let lastIndex = indexPath.row - 1 + var lastModel: NRReadChapterCatalogModel? + if lastIndex >= 0 { + lastModel = self.dataArr[lastIndex] + } + + if lastModel?.is_lock == true { + + + } else { + let vc = NRNovelReaderViewController() + vc.novelId = id + vc.targetCatalogModel = model + self.navigationController?.pushViewController(vc, animated: true) + } + + } + +} + +extension NRNovelDetailCatalogViewController { + + private func requestDataArr() async { + guard let id = self.novelModel?.id else { return } + guard let list = await NRNovelAPI.requestChapterCatalogList(id: id) else { return } + + self.dataArr = list + self.tableView.reloadData() + } + +} diff --git a/ReaderHive/Class/Novel/VC/NRNovelDetailRecommandViewController.swift b/ReaderHive/Class/Novel/VC/NRNovelDetailRecommandViewController.swift new file mode 100644 index 0000000..6d16e7f --- /dev/null +++ b/ReaderHive/Class/Novel/VC/NRNovelDetailRecommandViewController.swift @@ -0,0 +1,106 @@ +// +// NRNovelDetailRecommandViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/27. +// + +import UIKit +import SnapKit + +class NRNovelDetailRecommandViewController: NRViewController { + + lazy var dataArr: [NRNovelModel] = [] { + didSet { + self.collectionView.reloadData() + } + } + + var contentHeight: CGFloat { + return self.collectionView.contentSize.height + 48 + collectionView.contentInset.bottom + } + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .semibold) + label.textColor = .black + label.text = "More Like This".localized + return label + }() + + lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let itemWidth = (UIScreen.width - 32 - 40) / 3 + let itemHeight = 150 / 100 * itemWidth + 68 + + let layout = UICollectionViewFlowLayout() + layout.itemSize = .init(width: itemWidth, height: itemHeight) + layout.minimumLineSpacing = 18 + layout.minimumInteritemSpacing = 20 + layout.sectionInset = .init(top: 0, left: 16, bottom: 0, right: 16) + return layout + }() + + lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.showsVerticalScrollIndicator = false + collectionView.contentInset = .init(top: 0, left: 0, bottom: 10, right: 0) + collectionView.register(NRNovelDetailMoreLikeCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.backgroundImageView.isHidden = true + self.view.backgroundColor = .clear + + nr_setupUI() + } +} + +extension NRNovelDetailRecommandViewController { + + private func nr_setupUI() { + view.addSubview(titleLabel) + view.addSubview(collectionView) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview().offset(12) + make.height.equalTo(24) + } + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(48) + } + } + +} + + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRNovelDetailRecommandViewController: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRNovelDetailMoreLikeCell + cell.model = 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 = dataArr[indexPath.row] + let vc = NRNovelDetailViewController() + vc.novelId = model.id ?? "" + self.navigationController?.pushViewController(vc, animated: true) + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + didScrollCallback?(scrollView) + } +} diff --git a/ReaderHive/Class/Novel/VC/NRNovelDetailViewController.swift b/ReaderHive/Class/Novel/VC/NRNovelDetailViewController.swift new file mode 100644 index 0000000..ff79294 --- /dev/null +++ b/ReaderHive/Class/Novel/VC/NRNovelDetailViewController.swift @@ -0,0 +1,250 @@ +// +// NRNovelDetailViewController.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit +import SnapKit + +class NRNovelDetailViewController: NRViewController { + + var novelId: String { + set { + viewModel.novelId = newValue + } + get { + return viewModel.novelId + } + } + + lazy var viewModel = NRNovelDetailViewModel() + + private var navigationStyleScale: CGFloat = 0 + + lazy var coverBgImageView: NRImageView = { + let imageView = NRImageView() + imageView.nr_addEffectView(style: .dark) + return imageView + }() + + lazy var bottomView: NRNovelDetailBottomView = { + let view = NRNovelDetailBottomView() + view.viewModel = viewModel + return view + }() + + lazy var tableView: NRTableView = { + let tableView = NRTableView(frame: .zero, style: .plain) + tableView.shouldRecognizeSimultaneously = true + tableView.dataSource = self + tableView.delegate = self + tableView.separatorStyle = .none + tableView.showsVerticalScrollIndicator = false + tableView.register(NRTableViewCell.self, forCellReuseIdentifier: "cell") + return tableView + }() + + lazy var headerView: NRNovelDetailHeaderView = { + let view = NRNovelDetailHeaderView() + view.didChangeHeight = { [weak self] in + self?.updateHeaderViewHeight() + } + return view + }() + + lazy var recommandVC: NRNovelDetailRecommandViewController = { + let vc = NRNovelDetailRecommandViewController() + vc.didScrollCallback = { [weak self] scrollView in + self?.childContentOffsetDidChange(scrollView) + } + return vc + }() + + var recommandListView: UIScrollView { + return recommandVC.collectionView + } + + deinit { + recommandListView.removeObserver(self, forKeyPath: "contentSize") + } + + override func viewDidLoad() { + super.viewDidLoad() + self.edgesForExtendedLayout = [.top] + self.view.backgroundColor = .white + + recommandListView.addObserver(self, forKeyPath: "contentSize", context: nil) + + nr_setupUI() + + Task { + await self.viewModel.requestDetailData() + self.coverBgImageView.nr_setImage(self.viewModel.novelModel?.image_url) + self.headerView.model = self.viewModel.novelModel + + await self.viewModel.requestRecommandData() + self.recommandVC.dataArr = self.viewModel.recommandDataArr ?? [] + } + + updateHeaderViewHeight() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + updateNavigationStyle() + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if self.recommandListView == object as? UIScrollView { + if keyPath == "contentSize" { + self.tableView.reloadData() + } + } + } + + private func updateHeaderViewHeight() { + headerView.frame = .init(x: 0, y: 0, width: UIScreen.width, height: headerView.contentHeight) + tableView.tableHeaderView = headerView + self.tableView.reloadData() + } + + private func coverBgZoom() { + let contentOffsetY = self.tableView.contentOffset.y + if contentOffsetY >= 0 { + self.coverBgImageView.transform = CGAffineTransform.identity + } else { + let scale = 1 + (-contentOffsetY / coverBgImageView.bounds.height) * 1.3 + self.coverBgImageView.transform = CGAffineTransform(scaleX: scale, y: scale) + } + } + + +} + + +extension NRNovelDetailViewController { + + private func nr_setupUI() { + addChild(recommandVC) + + view.addSubview(coverBgImageView) + view.addSubview(bottomView) + view.addSubview(tableView) + + coverBgImageView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.height.equalTo(400 + UIScreen.navBarHeight) + } + + bottomView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + } + + tableView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.bottom.equalTo(bottomView.snp.top) + make.top.equalToSuperview() + } + } + +} + + + +//MARK: UITableViewDataSource UITableViewDelegate +extension NRNovelDetailViewController: UITableViewDataSource, UITableViewDelegate { + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! NRTableViewCell + cell.contentView.backgroundColor = self.view.backgroundColor + if recommandVC.view.superview == nil { + cell.contentView.addSubview(recommandVC.view) + recommandVC.view.snp.remakeConstraints { make in + make.edges.equalToSuperview() + } + } + return cell + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + let minHeight = UIScreen.height - bottomView.bounds.height - self.headerView.bounds.height + let maxHeight = UIScreen.height - bottomView.bounds.height - UIScreen.navBarHeight + let contentHeight = self.recommandVC.contentHeight + + if contentHeight < minHeight { + return minHeight + } + if contentHeight > maxHeight { + return maxHeight + } + return contentHeight + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + let contentOffsetY = scrollView.contentOffset.y + let maxOffsetY = self.tableViewMaxContentOffsetY() + //顶部图片缩放 + coverBgZoom() + + + //导航样式 + let scale = contentOffsetY / 240 + if scale > 1 { + navigationStyleScale = 1 + } else if scale < 0 { + navigationStyleScale = 0 + } + navigationStyleScale = scale + updateNavigationStyle() + + if contentOffsetY > maxOffsetY { + scrollView.contentOffset = .init(x: 0, y: maxOffsetY) + } + + if self.recommandListView.contentOffset.y > 0 { + self.setMainTableViewToMaxContentOffsetY() + } + } + +} + +extension NRNovelDetailViewController { + + private func tableViewMaxContentOffsetY() -> CGFloat { + let contentSizeH = tableView.contentSize.height + let maxOffsetY = contentSizeH - tableView.bounds.height + return maxOffsetY + } + + func setMainTableViewToMaxContentOffsetY() { + tableView.contentOffset = CGPoint(x: 0, y: tableViewMaxContentOffsetY()) + } + + private func updateNavigationStyle() { + if self.navigationStyleScale > 0.9 { + configNavigationBack("arrow_left_icon_02") + self.title = self.viewModel.novelModel?.name + } else { + configNavigationBack("arrow_left_icon_01") + self.title = nil + } + nr_setNavigationStyle(backgroundColor: .white.withAlphaComponent(CGFloat(self.navigationStyleScale)), titleColor: UINavigationBar.titleBlackColor) + } + + private func childContentOffsetDidChange(_ scrollView: UIScrollView) { + + if self.tableView.contentOffset.y < self.tableViewMaxContentOffsetY() { + scrollView.contentOffset = .zero + } else { + self.setMainTableViewToMaxContentOffsetY() + } + } + +} diff --git a/ReaderHive/Class/Novel/VC/Read/NRNovelReadBaseViewController.swift b/ReaderHive/Class/Novel/VC/Read/NRNovelReadBaseViewController.swift new file mode 100644 index 0000000..4cb6dd6 --- /dev/null +++ b/ReaderHive/Class/Novel/VC/Read/NRNovelReadBaseViewController.swift @@ -0,0 +1,35 @@ +// +// NRNovelReadBaseViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/3. +// + +import UIKit + +class NRNovelReadBaseViewController: NRViewController { + + weak var viewModel: NRNovelReadViewModel? + ///章节目录数据 + var catalogModel: NRReadChapterCatalogModel? + ///当前页面数据 + var pageModel: NRReadPageModel? + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/ReaderHive/Class/Novel/VC/Read/NRNovelReadContentViewController.swift b/ReaderHive/Class/Novel/VC/Read/NRNovelReadContentViewController.swift new file mode 100644 index 0000000..f7d437f --- /dev/null +++ b/ReaderHive/Class/Novel/VC/Read/NRNovelReadContentViewController.swift @@ -0,0 +1,81 @@ +// +// NRNovelReadContentViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/27. +// + +import UIKit +import SnapKit + +class NRNovelReadContentViewController: NRNovelReadBaseViewController { + + + lazy var topView: NRNovelReadContentTopView = { + let view = NRNovelReadContentTopView() + return view + }() + + lazy var bottomView: NRNovelReadContentBottomView = { + let view = NRNovelReadContentBottomView() + return view + }() + + lazy var readView: NRNovelReadView = { + let view = NRNovelReadView() + return view + }() + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidLoad() { + super.viewDidLoad() + self.backgroundImageView.isHidden = true + self.view.backgroundColor = .clear + + NotificationCenter.default.addObserver(self, selector: #selector(didChangedThemeNotification), name: NRNovelReadSetManager.didChangedThemeNotification, object: nil) + + + topView.viewModel = self.viewModel + topView.catalogModel = self.catalogModel + topView.pageModel = self.pageModel + bottomView.viewModel = self.viewModel + bottomView.catalogModel = self.catalogModel + bottomView.pageModel = self.pageModel + + readView.content = pageModel?.showContent + + nr_setupUI() + } + + @objc private func didChangedThemeNotification() { + readView.content = pageModel?.showContent + } + +} + +extension NRNovelReadContentViewController { + + private func nr_setupUI() { + view.addSubview(topView) + view.addSubview(bottomView) + view.addSubview(readView) + + topView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + } + + bottomView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + } + + readView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalTo(topView.snp.bottom) + make.bottom.equalTo(bottomView.snp.top) + } + } + +} diff --git a/ReaderHive/Class/Novel/VC/Read/NRNovelReadFinishViewController.swift b/ReaderHive/Class/Novel/VC/Read/NRNovelReadFinishViewController.swift new file mode 100644 index 0000000..0af7df7 --- /dev/null +++ b/ReaderHive/Class/Novel/VC/Read/NRNovelReadFinishViewController.swift @@ -0,0 +1,144 @@ +// +// NRNovelReadFinishViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/3. +// + +import UIKit +import SnapKit + +class NRNovelReadFinishViewController: NRNovelReadBaseViewController { + + + lazy var dataArr: [NRNovelModel] = [] { + didSet { + self.collectionView.reloadData() + } + } + + private lazy var backButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.viewModel?.vc?.navigationController?.popViewController(animated: true) + })) + button.setImage(UIImage(named: "arrow_left_icon_05"), for: .normal) + + return button + }() + + private lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let itemWidth = (UIScreen.width - 32 - 40) / 3 + let itemHeight = 150 / 100 * itemWidth + 68 + + let layout = UICollectionViewFlowLayout() + layout.itemSize = .init(width: itemWidth, height: itemHeight) + layout.minimumLineSpacing = 18 + layout.minimumInteritemSpacing = 20 + layout.sectionInset = .init(top: 0, left: 16, bottom: 0, right: 16) + layout.headerReferenceSize = .init(width: UIScreen.width, height: 390) + return layout + }() + + private lazy var collectionView: NRCollectionView = { + let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) + collectionView.register(NRNovelDetailMoreLikeCell.self, forCellWithReuseIdentifier: "cell") + collectionView.register(NRNovelReadFinishHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header") + return collectionView + }() + + override func viewDidLoad() { + super.viewDidLoad() + backgroundImageView.image = UIImage(named: "bg_image_04") + self.view.backgroundColor = .white + + +// let tap = UITapGestureRecognizer { _ in +// +// } +// self.view.addGestureRecognizer(tap) + +// self.view.addGestureRecognizer(UITapGestureRecognizer(actionBlock: { _ in +// +// })) + + nr_setupUI() + + Task { + await requestDataArr() + } + } + + override var prefersStatusBarHidden: Bool { + return false + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .darkContent + } + +} + +extension NRNovelReadFinishViewController { + + private func nr_setupUI() { + view.addSubview(backButton) + view.addSubview(collectionView) + + backButton.snp.makeConstraints { make in + make.left.equalToSuperview().offset(6) + make.height.equalTo(44) + make.top.equalTo(UIScreen.safeTop) + make.width.equalTo(36) + } + + collectionView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + make.bottom.equalToSuperview() + } + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension NRNovelReadFinishViewController: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRNovelDetailMoreLikeCell + cell.model = self.dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "header", for: indexPath) as! NRNovelReadFinishHeaderView + view.novelModel = self.viewModel?.novelModel + return view + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let model = dataArr[indexPath.row] + let vc = NRNovelDetailViewController() + vc.novelId = model.id ?? "" + self.viewModel?.vc?.navigationController?.pushViewController(vc, animated: true) + } + + +} + +extension NRNovelReadFinishViewController { + + private func requestDataArr() async { + guard let list = await NRNovelAPI.requestDetailRecommandData() else { return } + self.dataArr = list + self.collectionView.reloadData() + } + +} diff --git a/ReaderHive/Class/Novel/VC/Read/NRNovelReadPageViewController.swift b/ReaderHive/Class/Novel/VC/Read/NRNovelReadPageViewController.swift new file mode 100644 index 0000000..8875e68 --- /dev/null +++ b/ReaderHive/Class/Novel/VC/Read/NRNovelReadPageViewController.swift @@ -0,0 +1,104 @@ +// +// NRNovelReadPageViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/27. +// + +import UIKit +import YYCategories + + +@objc protocol NRNovelReadPageViewControllerDelegate: NSObjectProtocol { + + /// 获取上一页 + @objc optional func pageViewController(_ pageViewController: NRNovelReadPageViewController, getViewControllerBefore viewController: UIViewController?) + + /// 获取下一页 + @objc optional func pageViewController(_ pageViewController: NRNovelReadPageViewController, getViewControllerAfter viewController: UIViewController?) +} + +class NRNovelReadPageViewController: UIPageViewController, UIGestureRecognizerDelegate { + + ///点击区域 + let tapAreaWidth = UIScreen.width / 3 + + weak var nrDelegate: NRNovelReadPageViewControllerDelegate? + + private var allowTap = true + + // 自定义Tap手势 + private(set) lazy var customTapGestureRecognizer: UITapGestureRecognizer = { + let tap = UITapGestureRecognizer(target: self, action: #selector(touchTap(tap:))) + tap.delegate = self + return tap + }() + + private var scrollView: UIScrollView? + + override func viewDidLoad() { + super.viewDidLoad() + + for subview in self.view.subviews { + if let scrollView = subview as? UIScrollView { + self.scrollView = scrollView + break + } + } + + view.addGestureRecognizer(customTapGestureRecognizer) + } + + override func setViewControllers(_ viewControllers: [UIViewController]?, direction: UIPageViewController.NavigationDirection, animated: Bool, completion: ((Bool) -> Void)? = nil) { + self.scrollView?.isScrollEnabled = false + self.allowTap = false + super.setViewControllers(viewControllers, direction: direction, animated: animated) { [weak self] finish in + guard let self = self else { return } + self.scrollView?.isScrollEnabled = true + self.allowTap = true + completion?(finish) + } + } + + // tap事件 + @objc func touchTap(tap: UIGestureRecognizer) { + guard self.allowTap else { return } + + let touchPoint = tap.location(in: view) + + if (touchPoint.x < tapAreaWidth) { // 左边 + + nrDelegate?.pageViewController?(self, getViewControllerBefore: viewControllers?.first) + + }else if (touchPoint.x > (UIScreen.width - tapAreaWidth)) { // 右边 + + nrDelegate?.pageViewController?(self, getViewControllerAfter: viewControllers?.first) + } + } + + // MARK: UIGestureRecognizerDelegate + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + + if (gestureRecognizer.isKind(of: UITapGestureRecognizer.classForCoder()) && gestureRecognizer.isEqual(customTapGestureRecognizer)) { + + let touchPoint = customTapGestureRecognizer.location(in: view) + + if (touchPoint.x > tapAreaWidth && touchPoint.x < (UIScreen.width - tapAreaWidth)) { + return true + } + } + + return false + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + + if let _ = touch.view?.viewController as? NRNovelReadFinishViewController { + return false + } + return true + } + +} + diff --git a/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController+Page.swift b/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController+Page.swift new file mode 100644 index 0000000..c07c107 --- /dev/null +++ b/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController+Page.swift @@ -0,0 +1,169 @@ +// +// NRNovelReaderViewController+Page.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/27. +// + +import UIKit + +extension NRNovelReaderViewController { + + ///手动翻页 + func setViewController(displayController: UIViewController, isAbove:Bool, animated:Bool, dismissMenu: Bool = true) { + + let direction:UIPageViewController.NavigationDirection = isAbove ? .reverse : .forward + self.pageViewController.setViewControllers([displayController], direction: direction, animated: animated) + + + + if dismissMenu { + self.viewModel.showAllMenuView(isShow: false) + } + + pageFinish(displayController) + } + + ///更新当前索引 + private func updateCurrentIndexPath(_ viewController: UIViewController?) { + if let vc = viewController as? NRNovelReadBaseViewController { + self.viewModel.currentPageIndexPath = vc.pageModel?.indexPath ?? IndexPath(row: 0, section: 0) + } + } + + ///翻页完成 + private func pageFinish(_ viewController: UIViewController?) { + self.updateCurrentIndexPath(viewController) + + self.viewModel.preloadChapterData() + + self.bottomView.updateProgress() + + self.topView.reloadData() + + self.viewModel.saveReadRecord() + + self.setNeedsStatusBarAppearanceUpdate() + + if let vc = viewController as? NRNovelReadBaseViewController { + let model = vc.catalogModel + let lockCoins = model?.coins ?? 0 + let myCoins = NRLoginManager.manager.userInfo?.totalCoins ?? 0 + if model?.is_lock == true { + if lockCoins > myCoins { //弹出支付页面 + self.viewModel.openRechargeView() + } + } + } + } + +} + +extension NRNovelReaderViewController: NRNovelReadPageViewControllerDelegate { + + func pageViewController(_ pageViewController: NRNovelReadPageViewController, getViewControllerBefore viewController: UIViewController?) { + if let vc = getAboveReadController() { + setViewController(displayController: vc, isAbove: true, animated: true) + } + } + ///点击进入下一页 + func pageViewController(_ pageViewController: NRNovelReadPageViewController, getViewControllerAfter viewController: UIViewController?) { + if let vc = getBelowReadController() { + setViewController(displayController: vc, isAbove: false, animated: true) + } + } +} + + +extension NRNovelReaderViewController: UIPageViewControllerDelegate { + + ///翻页完成 + func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { + if completed { + pageFinish(pageViewController.viewControllers?.first) + } + } + + func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) { + self.viewModel.showAllMenuView(isShow: false) + } + +} + +extension NRNovelReaderViewController : UIPageViewControllerDataSource { + + ///上一页 + func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { + + return getAboveReadController() + +// tempNumber -= 1 + +// if abs(tempNumber) % 2 == 0 { // 背面 +// +// recordModel = GetAboveReadRecordModel(recordModel: recordModel) +// +// return GetReadViewBGController(recordModel: recordModel) +// +// }else{ // 内容 +// +// return GetReadViewController(recordModel: recordModel) +// } + } + + ///下一页 + func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { + return getBelowReadController() + +// tempNumber += 1 + +// if abs(tempNumber) % 2 == 0 { // 背面 +// +// return GetReadViewBGController(recordModel: recordModel) +// +// }else{ // 内容 +// +// recordModel = GetBelowReadRecordModel(recordModel: recordModel) +// +// return GetReadViewController(recordModel: recordModel) +// } + } + +} + + +extension NRNovelReaderViewController { + + func getReadController(catalogModel: NRReadChapterCatalogModel?, pageModel: NRReadPageModel?) -> UIViewController? { + guard let catalogModel = catalogModel, let pageModel = pageModel else { return nil } + let vc: NRNovelReadBaseViewController + if pageModel.pageType == .readFinish { + vc = NRNovelReadFinishViewController() + } else { + vc = NRNovelReadContentViewController() + } + vc.viewModel = self.viewModel + vc.catalogModel = catalogModel + vc.pageModel = pageModel + return vc + } + + ///获取当前页面控制器 + func getCurrentReadController() -> UIViewController? { + let (catalogModel, pageModel) = self.viewModel.getCurrentPageData() + return getReadController(catalogModel: catalogModel, pageModel: pageModel) + } + + ///获取下一页控制器 + func getBelowReadController() -> UIViewController? { + let (catalogModel, pageModel) = self.viewModel.getBelowPageData() + return getReadController(catalogModel: catalogModel, pageModel: pageModel) + } + + ///获取上一页控制器 + func getAboveReadController() -> UIViewController? { + let (catalogModel, pageModel) = self.viewModel.getAbovePageData() + return getReadController(catalogModel: catalogModel, pageModel: pageModel) + } + +} diff --git a/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController.swift b/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController.swift new file mode 100644 index 0000000..1e547b6 --- /dev/null +++ b/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController.swift @@ -0,0 +1,236 @@ +// +// NRNovelReaderViewController.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/26. +// + +import UIKit +import SnapKit +import FDFullscreenPopGesture + +class NRNovelReaderViewController: NRViewController { + + var novelId: String { + set { + self.viewModel.novelId = newValue + } + get { + return self.viewModel.novelId + } + } + + var novelModel: NRNovelModel? { + set { + self.viewModel.novelModel = newValue + } + get { + return self.viewModel.novelModel + } + } + + var targetCatalogModel: NRReadChapterCatalogModel? { + set { + self.viewModel.targetCatalogModel = newValue + } + get { + return self.viewModel.targetCatalogModel + } + } + + private(set) lazy var viewModel: NRNovelReadViewModel = { + let vm = NRNovelReadViewModel() + return vm + }() + + private(set) lazy var topView: NRNovelReadTopView = { + let view = NRNovelReadTopView() + view.viewModel = self.viewModel + return view + }() + + private(set) lazy var bottomView: NRNovelReadBottomView = { + let view = NRNovelReadBottomView() + view.viewModel = self.viewModel + return view + }() + + private(set) lazy var pageViewController: NRNovelReadPageViewController = { + let options = [UIPageViewController.OptionsKey.spineLocation : NSNumber(value: UIPageViewController.SpineLocation.min.rawValue)] + + let vc = NRNovelReadPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: options) + vc.view.backgroundColor = .clear + vc.delegate = self + vc.dataSource = self + vc.nrDelegate = self + vc.isDoubleSided = false + return vc + }() + + private lazy var readContentView: UIView = { + let view = UIView() + return view + }() + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidLoad() { + super.viewDidLoad() + self.backgroundImageView.isHidden = true + self.fd_interactivePopDisabled = true + + NotificationCenter.default.addObserver(self, selector: #selector(didChangedThemeNotification), name: NRNovelReadSetManager.didChangedThemeNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(didChangedTextLayoutNotification), name: NRNovelReadSetManager.didChangedTextLayoutNotification, object: nil) + self.view.backgroundColor = NRNovelReadSetManager.manager.currentTheme.backgroundColor + + viewModel.vc = self + viewModel.topView = topView + viewModel.bottomView = bottomView + viewModel.contentView = readContentView + + + nr_setupUI() + + loadData() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + Task { + await NRLoginManager.manager.updateUserInfo() + } + + } + + override var prefersStatusBarHidden: Bool { + return !self.viewModel.showTop + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .darkContent + } + + override var childForStatusBarHidden: UIViewController? { + if let _ = self.pageViewController.viewControllers?.first as? NRNovelReadContentViewController { + return nil + } else { + return self.pageViewController.viewControllers?.first + } + } + + override var childForStatusBarStyle: UIViewController? { + if let _ = self.pageViewController.viewControllers?.first as? NRNovelReadContentViewController { + return nil + } else { + return self.pageViewController.viewControllers?.first + } + } + + @objc private func didChangedThemeNotification() { + self.view.backgroundColor = NRNovelReadSetManager.manager.currentTheme.backgroundColor + } + + @objc private func didChangedTextLayoutNotification() { + self.viewModel.parserAllDataAndReloadPage(dismissMenu: false) + } + +} + +extension NRNovelReaderViewController { + + private func nr_setupUI() { + view.addSubview(readContentView) + view.addSubview(topView) + view.addSubview(bottomView) + readContentView.addSubview(pageViewController.view) + + readContentView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + topView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.bottom.equalTo(self.view.snp.top) + } + + bottomView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalTo(self.view.snp.bottom) + } + + pageViewController.view.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + } + +} + +extension NRNovelReaderViewController { + + ///装载数据 + private func loadData() { + Task { + //获取小说数据 + await self.viewModel.requestNovelDetail() + //获取目录数据 + await self.viewModel.requestChapterCatalogList() + + guard !self.viewModel.chapterCatalogList.isEmpty else { return } + + //读取历史记录 + let recordModel = self.viewModel.getReadRecord() + + //匹配历史记录的索引 + var section = 0 + var row = 0 + if let catalogModel = self.targetCatalogModel { + for (index, model) in self.viewModel.chapterCatalogList.enumerated() { + if model.id == catalogModel.id { + section = index + break + } + } + if catalogModel.id == recordModel?.short_play_video_id { + row = recordModel?.page ?? 0 + } + self.targetCatalogModel = nil + } else { + if let recordModel = recordModel { + for (index, model) in self.viewModel.chapterCatalogList.enumerated() { + if model.id == recordModel.short_play_video_id { + section = index + break + } + } + if let page = recordModel.page { + row = page + } + } + } + + + self.viewModel.currentPageIndexPath = IndexPath(row: row, section: section) + + let currentCatalogModel = self.viewModel.chapterCatalogList[section] + + //获取章节数据 + await self.viewModel.requestChapterData(currentCatalogModel) + + //校验索引,避免越界 + self.viewModel.checkCurrentIndexPath() + + if let vc = self.getCurrentReadController() { + setViewController(displayController: vc, isAbove: false, animated: false) + } + } + } + +} diff --git a/ReaderHive/Class/Novel/VM/NRNovelDetailViewModel.swift b/ReaderHive/Class/Novel/VM/NRNovelDetailViewModel.swift new file mode 100644 index 0000000..6ceae0e --- /dev/null +++ b/ReaderHive/Class/Novel/VM/NRNovelDetailViewModel.swift @@ -0,0 +1,34 @@ +// +// NRNovelDetailViewModel.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit + +class NRNovelDetailViewModel: NSObject { + + var novelId: String = "" + + @objc dynamic var novelModel: NRNovelModel? + + var recommandDataArr: [NRNovelModel]? + + func requestDetailData() async { + let (model, id, msg) = await NRNovelAPI.requestDetail(novelId) + + await MainActor.run { + if let model = model { + self.novelModel = model + } + } + } + + ///获取推荐数据 + func requestRecommandData() async { + guard let list = await NRNovelAPI.requestDetailRecommandData() else { return } + self.recommandDataArr = list + } + +} diff --git a/ReaderHive/Class/Novel/VM/NRNovelReadViewModel+Data.swift b/ReaderHive/Class/Novel/VM/NRNovelReadViewModel+Data.swift new file mode 100644 index 0000000..5938524 --- /dev/null +++ b/ReaderHive/Class/Novel/VM/NRNovelReadViewModel+Data.swift @@ -0,0 +1,264 @@ +// +// NRNovelReadViewModel+Data.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/5. +// + +import UIKit + +///阅读历史文件目录 +var kNRReadRecordFolderName: String { + let userId = NRLoginManager.manager.userInfo?.customer_id ?? "" + return "novel_read_record/\(userId)" +} + + +//MARK: 页面数据处理 +extension NRNovelReadViewModel { + + ///获取指定页面的数据 + func getPageData(_ indexPath: IndexPath) -> (NRReadChapterCatalogModel?, NRReadPageModel?) { + let section = indexPath.section + let row = indexPath.row + guard section >= 0, row >= 0 else { return (nil, nil) } + + guard chapterCatalogList.count > section else { return (nil, nil) } + let catalogModel = chapterCatalogList[section] + guard let chapterModel = catalogModel.chapterModel else { return (catalogModel, nil) } + let pageCount = chapterModel.pageList?.count ?? 0 + + guard pageCount > row, let pageModel = chapterModel.pageList?[row] else { return (catalogModel, nil) } + pageModel.indexPath = indexPath + return (catalogModel, pageModel) + } + + ///获取当前页面的数据 + func getCurrentPageData() -> (NRReadChapterCatalogModel?, NRReadPageModel?) { + return getPageData(self.currentPageIndexPath) + } + + ///获取下一页的数据 + func getBelowPageData() -> (NRReadChapterCatalogModel?, NRReadPageModel?) { + var section = self.currentPageIndexPath.section + var row = self.currentPageIndexPath.row + 1 + + let (catalogModel, pageModel) = getPageData(IndexPath(row: row, section: section)) + //下一页正常返回说明有下一页 + if let catalogModel = catalogModel, let pageModel = pageModel { + return (catalogModel, pageModel) + } + //没有下一页,进入下一章节 + section += 1 + row = 0 + return getPageData(IndexPath(row: row, section: section)) + } + + ///获取上一页的数据 + func getAbovePageData() -> (NRReadChapterCatalogModel?, NRReadPageModel?) { + var section = self.currentPageIndexPath.section + var row = self.currentPageIndexPath.row - 1 + + ///没有上一页,进入上一章节的最后一页 + if row < 0 { + section -= 1 + guard section >= 0 else { return (nil, nil) } + guard chapterCatalogList.count > section else { return (nil, nil) } + let catalogModel = chapterCatalogList[section] + row = (catalogModel.chapterModel?.pageList?.count ?? 0) - 1 + } + ///没有上一章节,说明已经是第一页 + if section < 0 { + return (nil, nil) + } + return getPageData(IndexPath(row: row, section: section)) + } + + ///重新解析全部数据 并更新页面 + func parserAllDataAndReloadPage(dismissMenu: Bool = true) { + ///重新解析全部数据 + self.chapterCatalogList.forEach { + $0.chapterModel?.parser() + } + + self.checkCurrentIndexPath() + + if let vc = self.vc?.getCurrentReadController() { + self.vc?.setViewController(displayController: vc, isAbove: false, animated: false, dismissMenu: dismissMenu) + } + } + + ///检查当前索引是否合法,不合法修改至合法 + ///主要用于更新全部数据 + func checkCurrentIndexPath() { + var currentRow = self.currentPageIndexPath.row + var currentSection = self.currentPageIndexPath.section + + if chapterCatalogList.count <= currentSection { + currentSection = chapterCatalogList.count - 1 + } + + let catalogModel = chapterCatalogList[currentSection] + let listCount = catalogModel.chapterModel?.pageList?.count ?? 0 + + if listCount <= currentRow { + currentRow = listCount - 1 + } + self.currentPageIndexPath = IndexPath(row: currentRow, section: currentSection) + + } + + ///跳转至指定章节的指定进度 进度为0-1的小数 + func skip(chapterIndex: Int, chapterProgress: CGFloat = 0, dismissMenu: Bool = true) { + let (catalogModel, _) = getPageData(IndexPath(row: 0, section: chapterIndex)) + + guard let catalogModel = catalogModel else { return } + + if let chapterModel = catalogModel.chapterModel { + + guard let displayController = vc?.getReadController(catalogModel: catalogModel, pageModel: chapterModel.pageList?.first) else { return } + self.vc?.setViewController(displayController: displayController, isAbove: false, animated: false, dismissMenu: dismissMenu) + + } else { //没有章节数据,先获取章节数据 + Task { + guard let _ = await self.requestChapterData(catalogModel) else { return } + await MainActor.run { + self.skip(chapterIndex: chapterIndex, chapterProgress: chapterProgress, dismissMenu: dismissMenu) + } + } + } + } + + ///跳转至下一章节 + func skipToNextChapter() { + let section = self.currentPageIndexPath.section + 1 + self.skip(chapterIndex: section, dismissMenu: false) + } + + ///跳转至上一章节 + func skipToPrevChapter() { + let section = self.currentPageIndexPath.section - 1 + self.skip(chapterIndex: section, dismissMenu: false) + } + + ///保存阅读记录 + func saveReadRecord() { + guard novelId.count > 0 else { return } + let (catalogModel, pageModel) = getCurrentPageData() + //未解锁章节不保存阅读记录 + guard catalogModel?.is_lock == false else { return } + guard let catalogModel = catalogModel, let pageModel = pageModel else { return } + guard pageModel.pageType == .textPart else { return } + + let model = NRNovelReadRecordModel() + model.short_play_video_id = catalogModel.id + model.episode = catalogModel.episode + model.page = pageModel.page?.intValue + NRKeyedArchiver.archiver(folderName: kNRReadRecordFolderName, fileName: novelId, object: model) + NRNovelAPI.requestUploadRecord(novelId, chapterId: catalogModel.id ?? "") + } + + ///获取阅读记录 + func getReadRecord() -> NRNovelReadRecordModel? { + guard novelId.count > 0 else { return nil } + var recordModel = NRKeyedArchiver.unarchiver(folderName: kNRReadRecordFolderName, fileName: novelId) as? NRNovelReadRecordModel + //匹配本地记录与网络记录 + if self.novelModel?.progress?.short_play_video_id != "0", self.novelModel?.progress?.short_play_video_id != recordModel?.short_play_video_id { + recordModel = self.novelModel?.progress + } + return recordModel + } +} + +//MARK: 网络请求 +extension NRNovelReadViewModel { + ///获取小说数据 + func requestNovelDetail() async { + let (model, _, _) = await NRNovelAPI.requestDetail(self.novelId) + guard let model = model else { return } + await MainActor.run { + self.novelModel = model + } + } + + ///获取章节目录 + func requestChapterCatalogList() async { + guard let list = await NRNovelAPI.requestChapterCatalogList(id: self.novelId) else { return } + await MainActor.run { + self.chapterCatalogList = list + self.addUnlockPageData() + } + } + ///获取章节数据 + @discardableResult + func requestChapterData(_ catalogModel: NRReadChapterCatalogModel) async -> NRReadChapterModel? { + guard let chapterId = catalogModel.id else { return nil } + + let myCoins = NRLoginManager.manager.userInfo?.totalCoins ?? 0 + let lockCoins = catalogModel.coins ?? 0 + + //金币不够解锁 + if catalogModel.is_lock == true, myCoins < lockCoins { + return nil + } + + let (model, code) = await NRNovelAPI.requestChapterData(novelId: self.novelId, chapterId: chapterId) + guard let model = model else { return nil } + + model.parser() + ///判断为最后一个章节 + if chapterId == self.chapterCatalogList.last?.id { + model.pageList?.append(NRReadPageModel.createReadFinishModel()) + } + + catalogModel.chapterModel = model + + if catalogModel.is_lock == true { + catalogModel.is_lock = false + self.addUnlockPageData() + } + return model + } + + ///预加载上下两页数据 + func preloadChapterData() { + Task { + await loadBelowChapterData() + await loadAboveChapterData() + } + } + + ///加载下一章节数据 + func loadBelowChapterData() async { + let (catalogModel, _) = getPageData(IndexPath(row: 0, section: self.currentPageIndexPath.section + 1)) + guard let catalogModel = catalogModel else { return } + + if catalogModel.chapterModel?.novel_txt == nil { + await requestChapterData(catalogModel) + } + + } + + ///加载上一章节数据 + func loadAboveChapterData() async { + let (catalogModel, _) = getPageData(IndexPath(row: 0, section: self.currentPageIndexPath.section - 1)) + guard let catalogModel = catalogModel else { return } + + if catalogModel.chapterModel?.novel_txt == nil { + await requestChapterData(catalogModel) + } + } + + ///给目录列表中增加未解锁页面数据 + func addUnlockPageData() { + for model in self.chapterCatalogList { + if model.is_lock == true { + let chapterModel = NRReadChapterModel() + chapterModel.parserEmpty() + model.chapterModel = chapterModel + break + } + } + } +} + diff --git a/ReaderHive/Class/Novel/VM/NRNovelReadViewModel.swift b/ReaderHive/Class/Novel/VM/NRNovelReadViewModel.swift new file mode 100644 index 0000000..e707f02 --- /dev/null +++ b/ReaderHive/Class/Novel/VM/NRNovelReadViewModel.swift @@ -0,0 +1,184 @@ +// +// NRNovelReadViewModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/27. +// + +import UIKit +import HWPanModal +import YYCategories + + + +class NRNovelReadViewModel: NSObject { + + var novelId: String = "" + @objc dynamic var novelModel: NRNovelModel? + ///目标数据,用于首次进入时,进入指定章节 + var targetCatalogModel: NRReadChapterCatalogModel? + + weak var vc: NRNovelReaderViewController? + weak var topView: NRNovelReadTopView? + weak var bottomView: NRNovelReadBottomView? + weak var contentView: UIView? { + didSet { + contentView?.addGestureRecognizer(singleTap) + } + } + + private(set) var showTop = false + ///章节目录列表 + lazy var chapterCatalogList: [NRReadChapterCatalogModel] = [] + + @objc dynamic lazy var currentPageIndexPath: IndexPath = IndexPath(row: 0, section: 0) + + /// 用于区分正反面的值(勿动) + var tempNumber = 1 + + ///单击手势 + private lazy var singleTap: UITapGestureRecognizer = { + let tap = UITapGestureRecognizer(target: self, action: #selector(touchSingleTap)) + tap.numberOfTapsRequired = 1 + tap.delegate = self + return tap + }() + +} + + + +extension NRNovelReadViewModel { + + ///退出阅读页面 + func backReadPage() { + Task { + guard let isShowModel = await NRNovelAPI.requestShowRecommendPop(id: self.novelId), isShowModel.is_pop_up == true else { + await _backReadPage() + return + } + + await MainActor.run { + let alert = NRAlert(title: "alert_title_01".localized, detail: "alert_detail_01".localized, topIconImage: UIImage(named: "alert_top_icon_01"), highlightButtonText: "Yes, Recommend".localized) + alert.closeHandle = { [weak self] in + guard let self = self else { return } + self._backReadPage() + } + alert.highlightHandle = { [weak self] in + guard let self = self else { return } + NRNovelAPI.requestConfirmRecommend(self.novelId) + self._backReadPage() + } + alert.show() + } + } + + + } + + @MainActor + private func _backReadPage() { + self.vc?.navigationController?.popViewController(animated: true) + } + + func showAllMenuView(isShow: Bool) { + self.showTopView(isShow: isShow) + self.showBottomView(isShow: isShow) + } + + ///显示顶部视图 + func showTopView(isShow: Bool) { + guard showTop != isShow else { return } + self.showTop = isShow + self.vc?.setNeedsStatusBarAppearanceUpdate() + + UIView.animate(withDuration: NRNovelReadSetManager.manager.animateDuration) { [weak self] in + guard let self = self else { return } + if isShow { + self.topView?.transform = CGAffineTransform(translationX: 0, y: NRNovelReadSetManager.manager.topViewHeight) + } else { + self.topView?.transform = CGAffineTransform.identity + } + } completion: { _ in + + } + } + + ///显示底部视图 + func showBottomView(isShow: Bool) { + + UIView.animate(withDuration: NRNovelReadSetManager.manager.animateDuration) { [weak self] in + guard let self = self else { return } + if isShow { + self.bottomView?.transform = CGAffineTransform(translationX: 0, y: -NRNovelReadSetManager.manager.bottomViewHeight) + } else { + self.bottomView?.transform = CGAffineTransform.identity + } + } completion: { _ in + + } + } + + ///显示更多工具栏 + func showMoreView() { +// let view = NRNovelReadMoreView() +// view.viewModel = self +// view.present(in: nil) + guard let model = self.novelModel else { return } + + let view = NRNovelReadGradeView() + view.model = model + view.present(in: nil) + } + + ///展示设置页面 + func showSettingView() { + + let view = NRNovelReadSettingView() + view.present(in: nil) + } + + ///展示目录 + func showCatalogView() { + let (catalogModel, _) = self.getCurrentPageData() + + let view = NRNovelReaderCatalogView() + view.novelModel = self.novelModel + view.currentCatalogModel = catalogModel + view.catalogDataArr = self.chapterCatalogList + view.didSelected = { [weak self] index in + guard let self = self else { return } + if index != self.currentPageIndexPath.section { + self.skip(chapterIndex: index) + } + } + view.show() + } + + ///打开充值页面 + func openRechargeView() { +// let view = NRDetailRechargeView() +// view.present(in: nil) + } + +} + +//MARK: UIGestureRecognizerDelegate +extension NRNovelReadViewModel: UIGestureRecognizerDelegate { + + @objc private func touchSingleTap() { + showAllMenuView(isShow: !showTop) + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + + if let _ = touch.view?.viewController as? NRNovelReadFinishViewController { + return false + } + return true + } + +} + + + diff --git a/ReaderHive/Delegate/AppDelegate+Config.swift b/ReaderHive/Delegate/AppDelegate+Config.swift new file mode 100644 index 0000000..c3376c1 --- /dev/null +++ b/ReaderHive/Delegate/AppDelegate+Config.swift @@ -0,0 +1,30 @@ +// +// AppDelegate+Config.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// +import UIKit +import IQKeyboardManagerSwift +import IQKeyboardToolbarManager + + +extension AppDelegate { + + func setConfig() { + NRToast.config() + UIView.fa_Awake() + + IQKeyboardManager.shared.isEnabled = true + IQKeyboardManager.shared.resignOnTouchOutside = true + IQKeyboardToolbarManager.shared.isEnabled = false + + let appearance = UINavigationBar.defaultAppearance() + UINavigationBar.appearance().scrollEdgeAppearance = appearance + UINavigationBar.appearance().standardAppearance = appearance + + ///允许获取电量 + UIDevice.current.isBatteryMonitoringEnabled = true + } + +} diff --git a/ReaderHive/Delegate/AppDelegate.swift b/ReaderHive/Delegate/AppDelegate.swift new file mode 100644 index 0000000..65bd1d9 --- /dev/null +++ b/ReaderHive/Delegate/AppDelegate.swift @@ -0,0 +1,52 @@ +// +// AppDelegate.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + NRTool.appDelegate = self + NRNetworkReachableManager.manager.startMonitoring() + + NotificationCenter.default.addObserver(self, selector: #selector(networkStatusDidChangeNotification), name: NRNetworkReachableManager.networkStatusDidChangeNotification, object: nil) + + setConfig() + + Task { + await NRLoginManager.manager.updateUserInfo() + } + + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + + @objc private func networkStatusDidChangeNotification() { + Task { + await NRLoginManager.manager.updateUserInfo() + } + } + +} + diff --git a/ReaderHive/Delegate/SceneDelegate.swift b/ReaderHive/Delegate/SceneDelegate.swift new file mode 100644 index 0000000..36f7bbc --- /dev/null +++ b/ReaderHive/Delegate/SceneDelegate.swift @@ -0,0 +1,55 @@ +// +// SceneDelegate.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + NRTool.sceneDelegate = self + guard let windowScene = (scene as? UIWindowScene) else { return } + NRTool.windowScene = windowScene + + window = UIWindow(windowScene: windowScene) + window?.rootViewController = NRTabBarController() + window?.makeKeyAndVisible() + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/ReaderHive/Libs/Alert/NRAlert.swift b/ReaderHive/Libs/Alert/NRAlert.swift new file mode 100644 index 0000000..1666953 --- /dev/null +++ b/ReaderHive/Libs/Alert/NRAlert.swift @@ -0,0 +1,142 @@ +// +// NRAlert.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/5. +// + +import UIKit +import SnapKit + +class NRAlert: NRBaseAlert { + + var closeHandle: (() -> Void)? + var highlightHandle: (() -> Void)? + + private lazy var bgView: UIImageView = { + let view = UIImageView(image: UIImage(named: "alert_bg_image_01")) + view.isUserInteractionEnabled = true + return view + }() + + private lazy var topIconImageView: UIImageView = { + let imageView = UIImageView() + return imageView + }() + + private lazy var closeButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + self?.closeHandle?() + self?.dismiss() + })) + button.setImage(UIImage(named: "close_icon_01"), for: .normal) + return button + }() + + + + init(title: String?, detail: String?, topIconImage: UIImage?, highlightButtonText: String?) { + super.init(frame: .zero) + nr_setupUI() + + topIconImageView.image = topIconImage + + let stackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .center + stackView.spacing = 12 + + if let title = title { + let titleLabel = UILabel() + titleLabel.textAlignment = .center + titleLabel.numberOfLines = 0 + titleLabel.font = .font(ofSize: 20, weight: .medium) + titleLabel.textColor = .F_9710_D + titleLabel.text = title + + stackView.addArrangedSubview(titleLabel) + } + + if let detail = detail { + let label = UILabel() + label.textAlignment = .center + label.numberOfLines = 0 + label.font = .font(ofSize: 14, weight: .regular) + label.textColor = .black.withAlphaComponent(0.5) + label.text = detail + stackView.addArrangedSubview(label) + } + + let buttonStackView = UIStackView() + buttonStackView.axis = .horizontal + buttonStackView.spacing = 19 + buttonStackView.distribution = .fillEqually + + + if let text = highlightButtonText { + let button = NRGradientButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.highlightHandle?() + self.dismiss() + })) + button.setTitle(text, for: .normal) + button.setTitleColor(.white, for: .normal) + button.titleLabel?.font = .font(ofSize: 14, weight: .medium) + 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 + buttonStackView.addArrangedSubview(button) + } + + bgView.addSubview(stackView) + bgView.addSubview(buttonStackView) + + stackView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.right.lessThanOrEqualToSuperview().offset(-16) + make.top.equalToSuperview().offset(100) + } + + buttonStackView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + make.top.equalTo(stackView.snp.bottom).offset(36) + make.bottom.equalToSuperview().offset(-16) + make.height.equalTo(48) + } + + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension NRAlert { + + private func nr_setupUI() { + contentView.addSubview(bgView) + contentView.addSubview(topIconImageView) + contentView.addSubview(closeButton) + + bgView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview() + } + + topIconImageView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(-16) + } + + closeButton.snp.makeConstraints { make in + make.centerX.bottom.equalToSuperview() + make.top.equalTo(bgView.snp.bottom).offset(24) + } + } + + +} diff --git a/ReaderHive/Libs/Alert/NRAlertWindowManager.swift b/ReaderHive/Libs/Alert/NRAlertWindowManager.swift new file mode 100644 index 0000000..490f2db --- /dev/null +++ b/ReaderHive/Libs/Alert/NRAlertWindowManager.swift @@ -0,0 +1,42 @@ +// +// NRAlertWindowManager.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/5. +// + +import UIKit + +class NRAlertWindowManager: NSObject { + static let manager = NRAlertWindowManager() + + private(set) var window: UIWindow? + + func createWindow() -> UIWindow { + guard let window = window else { + let window = UIWindow(windowScene: NRTool.windowScene!) + window.backgroundColor = .clear + window.windowLevel = .alert + window.isHidden = false + self.window = window + return window + } + return window + } + + func dismissWindow() { + guard let window = self.window else { return } + + var isHidden = true + + window.subviews.forEach { + if $0.isKind(of: NRBaseAlert.self) { + isHidden = false + } + } + if isHidden { + window.isHidden = true + self.window = nil + } + } +} diff --git a/ReaderHive/Libs/Alert/NRBaseAlert.swift b/ReaderHive/Libs/Alert/NRBaseAlert.swift new file mode 100644 index 0000000..e0a536c --- /dev/null +++ b/ReaderHive/Libs/Alert/NRBaseAlert.swift @@ -0,0 +1,75 @@ +// +// NRBaseAlert.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/5. +// + +import UIKit +import SnapKit + +class NRBaseAlert: UIView { + + private(set) var contentView: UIView = { + let view = UIView() + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = .black.withAlphaComponent(0.5) + + addSubview(contentView) + contentView.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @discardableResult + @objc func show(in view: UIView? = nil) -> Self { + guard self.superview == nil else { return self } + + var inView: UIView + if let view = view { + inView = view + } else { + inView = NRAlertWindowManager.manager.createWindow() + } + + inView.addSubview(self) + self.frame = inView.bounds + showAnimation() + + return self + } + @objc func dismiss() { + dismissAnimation() + } + +} + +extension NRBaseAlert { + private func showAnimation() { + contentView.transform = CGAffineTransform(translationX: 0, y: 200) + + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0) { + self.contentView.transform = CGAffineTransform.identity + } + } + + private func dismissAnimation() { + + UIView.animate(withDuration: 0.3) { + self.alpha = 0 + self.contentView.transform = CGAffineTransform(translationX: 0, y: 500) + } completion: { _ in + self.removeFromSuperview() + NRAlertWindowManager.manager.dismissWindow() + } + } +} + diff --git a/ReaderHive/Libs/DeviceId/NRDeviceId.swift b/ReaderHive/Libs/DeviceId/NRDeviceId.swift new file mode 100644 index 0000000..aa561ef --- /dev/null +++ b/ReaderHive/Libs/DeviceId/NRDeviceId.swift @@ -0,0 +1,25 @@ +// +// NRDeviceId.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit + +class NRDeviceId: NSObject { + + static let shared = NRDeviceId() + private let key = "com.reader_hive.deviceid" + + + lazy var id: String = { + if let savedID = NRKeychain.shared.read(key: key) { + return savedID + } else { + let newID = UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString + NRKeychain.shared.save(key: key, value: newID) + return newID + } + }() +} diff --git a/ReaderHive/Libs/DeviceId/NRKeychain.swift b/ReaderHive/Libs/DeviceId/NRKeychain.swift new file mode 100644 index 0000000..fc24750 --- /dev/null +++ b/ReaderHive/Libs/DeviceId/NRKeychain.swift @@ -0,0 +1,59 @@ +// +// NRKeychain.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit + +class NRKeychain: NSObject { + + static let shared = NRKeychain() + + func save(key: String, value: String) { + if let data = value.data(using: .utf8) { + // 先删除旧的 + let query = [ + kSecClass: kSecClassGenericPassword, + kSecAttrAccount: key + ] as CFDictionary + SecItemDelete(query) + + // 再保存新的 + let attributes = [ + kSecClass: kSecClassGenericPassword, + kSecAttrAccount: key, + kSecValueData: data, + kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock + ] as CFDictionary + + SecItemAdd(attributes, nil) + } + } + + func read(key: String) -> String? { + let query = [ + kSecClass: kSecClassGenericPassword, + kSecAttrAccount: key, + kSecReturnData: kCFBooleanTrue!, + kSecMatchLimit: kSecMatchLimitOne + ] as CFDictionary + + var dataTypeRef: AnyObject? + let status = SecItemCopyMatching(query, &dataTypeRef) + + if status == errSecSuccess, let data = dataTypeRef as? Data { + return String(data: data, encoding: .utf8) + } + return nil + } + + func delete(key: String) { + let query = [ + kSecClass: kSecClassGenericPassword, + kSecAttrAccount: key + ] as CFDictionary + SecItemDelete(query) + } +} diff --git a/ReaderHive/Libs/Empty/NREmpty.swift b/ReaderHive/Libs/Empty/NREmpty.swift new file mode 100644 index 0000000..80cc1e3 --- /dev/null +++ b/ReaderHive/Libs/Empty/NREmpty.swift @@ -0,0 +1,27 @@ +// +// NREmpty.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/25. +// + +import UIKit +import LYEmptyView + +struct NREmpty { + + static func nr_emptyView(image: UIImage? = UIImage(named: "empty_image_01"), title: String? = "No data".localized) -> LYEmptyView { + + let view = LYEmptyView.emptyActionView(with: image, titleStr: title, detailStr: nil, btnTitleStr: nil) { +// btnClickBlock?() + } + + view?.titleLabFont = .font(ofSize: 16, weight: .regular) + view?.titleLabTextColor = .black.withAlphaComponent(0.25) + view?.contentViewOffset = -100 + view?.subViewMargin = 16 + return view! + + } + +} diff --git a/ReaderHive/Libs/Hud/NRHud.swift b/ReaderHive/Libs/Hud/NRHud.swift new file mode 100644 index 0000000..9aa6e82 --- /dev/null +++ b/ReaderHive/Libs/Hud/NRHud.swift @@ -0,0 +1,22 @@ +// +// NRHud.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import SVProgressHUD + +struct NRHud { + + static func show(containerView: UIView? = nil) { + SVProgressHUD.setContainerView(containerView) + SVProgressHUD.setDefaultMaskType(.clear) + SVProgressHUD.show() + } + + static func dismiss() { + SVProgressHUD.dismiss() + } +} diff --git a/ReaderHive/Libs/Hud/NRToast.swift b/ReaderHive/Libs/Hud/NRToast.swift new file mode 100644 index 0000000..72a3997 --- /dev/null +++ b/ReaderHive/Libs/Hud/NRToast.swift @@ -0,0 +1,24 @@ +// +// NRToast.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import Toast + +struct NRToast { + + static func config() { + CSToastManager.setTapToDismissEnabled(false) + CSToastManager.setDefaultDuration(2) + CSToastManager.setDefaultPosition(CSToastPositionCenter) + } + + static func show(text: String?) { + guard let text = text else { return } + NRTool.keyWindow?.makeToast(text) + } + +} diff --git a/ReaderHive/Libs/KeyedArchiver/NRKeyedArchiver.swift b/ReaderHive/Libs/KeyedArchiver/NRKeyedArchiver.swift new file mode 100644 index 0000000..41a9623 --- /dev/null +++ b/ReaderHive/Libs/KeyedArchiver/NRKeyedArchiver.swift @@ -0,0 +1,96 @@ +// +// NRKeyedArchiver.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/3. +// + +import UIKit + +class NRKeyedArchiver: NSObject { + + static let documentDirectoryPath = (NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last! as String) + ///文件主目录 + static let mineFolderName = "ReaderHive" + + /// 归档文件 + class func archiver(folderName:String, fileName:String, object:AnyObject) { + + var path = documentDirectoryPath + "/\(mineFolderName)/\(folderName)" + + if creat_file(path: path) { // 创建文件夹成功或者文件夹存在 + + path += "/\(fileName)" + + NSKeyedArchiver.archiveRootObject(object, toFile: path) + } + } + + /// 解档文件 + class func unarchiver(folderName:String, fileName:String) ->AnyObject? { + + let path = documentDirectoryPath + "/\(mineFolderName)/\(folderName)/\(fileName)" + + return NSKeyedUnarchiver.unarchiveObject(withFile: path) as AnyObject? + } + + /// 删除归档文件 + class func remove(folderName:String, fileName:String? = nil) ->Bool { + + var path = documentDirectoryPath + "/\(mineFolderName)/\(folderName)" + + if fileName != nil && !fileName!.isEmpty { path += "/\(fileName!)" } + + do{ + try FileManager.default.removeItem(atPath: path) + + return true + + }catch{ } + + return false + } + + /// 清空归档文件 + class func clear() ->Bool { + + let path = documentDirectoryPath + "/\(mineFolderName)" + + do{ + + try FileManager.default.removeItem(atPath: path) + + return true + + }catch{ } + + return false + } + + /// 是否存在归档文件 + class func isExist(folderName:String, fileName:String? = nil) ->Bool { + + var path = documentDirectoryPath + "/\(mineFolderName)/\(folderName)" + + if fileName != nil && !fileName!.isEmpty { path += "/\(fileName!)" } + + return FileManager.default.fileExists(atPath: path) + } + + /// 创建文件夹,如果存在则不创建 + private class func creat_file(path:String) ->Bool { + + let fileManager = FileManager.default + + if fileManager.fileExists(atPath: path) { return true } + + do{ + try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) + + return true + + }catch{ } + + return false + } +} diff --git a/ReaderHive/Libs/LocalizedManager/NRLocalizedManager.swift b/ReaderHive/Libs/LocalizedManager/NRLocalizedManager.swift new file mode 100644 index 0000000..07c036d --- /dev/null +++ b/ReaderHive/Libs/LocalizedManager/NRLocalizedManager.swift @@ -0,0 +1,128 @@ +// +// NRLocalizedManager.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/8. +// + +import UIKit + +class NRLocalizedManager: NSObject { + static let shared = NRLocalizedManager() + private let LocalizedUserDefaultsKey = "NRLocalizedManager.LocalizedUserDefaultsKey" + private let LocalizedDataUserDefaultsKey = "NRLocalizedManager.LocalizedDataUserDefaultsKey" + private let LocalizedDataLocalizedKeyUserDefaultsKey = "NRLocalizedManager.LocalizedDataLocalizedKeyUserDefaultsKey" + + + ///多语言数据 + private(set) lazy var localizedData: [String : String]? = UserDefaults.standard.object(forKey: LocalizedDataUserDefaultsKey) as? [String : String] + { + didSet { + UserDefaults.standard.set(localizedData, forKey: LocalizedDataUserDefaultsKey) + UserDefaults.standard.synchronize() + } + } + ///当前语言数据对应的key + private(set) lazy var localizedDataLocalizedKey: String? = UserDefaults.standard.object(forKey: LocalizedDataLocalizedKeyUserDefaultsKey) as? String + { + didSet { + UserDefaults.standard.set(localizedDataLocalizedKey, forKey: LocalizedDataLocalizedKeyUserDefaultsKey) + UserDefaults.standard.synchronize() + } + } + + // 获取当前语言代码(如果用户未手动设置,则返回系统语言) + var currentLocalizedKey: String { + get { +// var key = (UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey) ?? Locale.preferredLanguages.first) ?? "en" +// if key.contains("zh-Hans") { +// key = "zh" +// } else if key.contains("zh-Hant") { +// key = "zh_hk" +// } else { +// let arr = key.components(separatedBy: "-") +// key = arr.first ?? "en" +// } +// return key + return "en" + } + set { + UserDefaults.standard.set(newValue, forKey: LocalizedUserDefaultsKey) + UserDefaults.standard.synchronize() + } + } + + var mjLocalizedKey: String { + let key = currentLocalizedKey + if key == "zh" { + return "zh-Hans" + } else if key == "zh_hk" { + return "zh-Hant" + } + return key + } + + // 判断是否跟随系统 + var isFollowingSystem: Bool { + return UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey) == nil + } + + // 还原为系统默认语言 + func resetToSystemLanguage() { + UserDefaults.standard.removeObject(forKey: LocalizedUserDefaultsKey) + UserDefaults.standard.synchronize() + } + + // 获取本地化字符串 + func localizedString(forKey key: String, tableName: String? = nil) -> String { + if let localizedData = localizedData, + let text = localizedData[key] { + return text + + } else if let selectedLanguage = UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey), + let bundlePath = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj"), + let bundle = Bundle(path: bundlePath) { + + return bundle.localizedString(forKey: key, value: nil, table: tableName) + } else { + return NSLocalizedString(key, tableName: tableName, bundle: .main, value: "", comment: "") + } + } +} + +extension NRLocalizedManager { + ///获取本地化数据 + func updateLocalizedData(key: String = NRLocalizedManager.shared.currentLocalizedKey) async -> Bool { + guard let model = await NRSettingAPI.requestLocalizedData(key: key) else { return false } + + + if let translates = model.translates { + self.localizedDataLocalizedKey = key + self.localizedData = translates + return true + } else { + return false + } + } +} + + + +extension NRLocalizedManager { + + static let localizedDidChange = Notification.Name(rawValue: "SPLocalizedManager.localizedDidChange") + +} + +extension String { + + var localized: String { + var text = NRLocalizedManager.shared.localizedString(forKey: self) + text = text.replacingOccurrences(of: "
", with: "\n") + return text + } + + func localizedReplace(text: String) -> String { + return self.localized.replacingOccurrences(of: "##", with: text) + } +} diff --git a/ReaderHive/Libs/LocalizedManager/NRLocalizedModel.swift b/ReaderHive/Libs/LocalizedManager/NRLocalizedModel.swift new file mode 100644 index 0000000..ec90323 --- /dev/null +++ b/ReaderHive/Libs/LocalizedManager/NRLocalizedModel.swift @@ -0,0 +1,16 @@ +// +// NRLocalizedModel.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/12/8. +// + +import UIKit +import SmartCodable + +class NRLocalizedModel: NSObject, SmartCodable { + + required override init() { } + + var translates: [String : String]? +} diff --git a/ReaderHive/Libs/Login/NRLoginManager.swift b/ReaderHive/Libs/Login/NRLoginManager.swift new file mode 100644 index 0000000..1d2ac9a --- /dev/null +++ b/ReaderHive/Libs/Login/NRLoginManager.swift @@ -0,0 +1,42 @@ +// +// NRLoginManager.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit + +class NRLoginManager: NSObject { + + static let manager = NRLoginManager() + + private(set) var token = UserDefaults.nr_object(forKey: kNRLoginTokenDefaultsKey, as: NRLoginToken.self) + private(set) var userInfo = UserDefaults.nr_object(forKey: kNRUserInfoDefaultsKey, as: NRUserInfo.self) + + func setAccountToken(_ token: NRLoginToken?) { + self.token = token + UserDefaults.nr_setObject(token, forKey: kNRLoginTokenDefaultsKey) + } + + + func updateUserInfo() async { + guard let userInfo = await NRUserAPI.requestUserInfo() else { return } + self.userInfo = userInfo + UserDefaults.nr_setObject(token, forKey: kNRUserInfoDefaultsKey) + + await MainActor.run { + NotificationCenter.default.post(name: NRLoginManager.userInfoUpdateNotification, object: nil) + } + } + +} + + +extension NRLoginManager { + + ///用户信息更新 + @objc static let userInfoUpdateNotification = Notification.Name(rawValue: "NRLoginManager.userInfoUpdateNotification") + ///登录状态发生变化 + @objc static let loginStateDidChangeNotification = Notification.Name(rawValue: "NRLoginManager.loginStateDidChangeNotification") +} diff --git a/ReaderHive/Libs/Login/NRLoginToken.swift b/ReaderHive/Libs/Login/NRLoginToken.swift new file mode 100644 index 0000000..c1e7b68 --- /dev/null +++ b/ReaderHive/Libs/Login/NRLoginToken.swift @@ -0,0 +1,40 @@ +// +// NRLoginToken.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import SmartCodable + +class NRLoginToken: NSObject, SmartCodable, NSSecureCoding { + + required override init() { } + + var auto_login: Int? + var token: String? + var customer_id: String? + + + static var supportsSecureCoding: Bool { + get { + return true + } + } + + func encode(with coder: NSCoder) { + coder.encode(token, forKey: "token") + coder.encode(customer_id, forKey: "customer_id") + coder.encode(auto_login, forKey: "auto_login") + } + + required init?(coder: NSCoder) { + super.init() + + token = coder.decodeObject(of: NSString.self, forKey: "token") as? String + customer_id = coder.decodeObject(of: NSString.self, forKey: "customer_id") as? String + auto_login = coder.decodeObject(of: NSNumber.self, forKey: "auto_login")?.intValue + } + +} diff --git a/ReaderHive/Libs/Login/NRUserInfo.swift b/ReaderHive/Libs/Login/NRUserInfo.swift new file mode 100644 index 0000000..122b32c --- /dev/null +++ b/ReaderHive/Libs/Login/NRUserInfo.swift @@ -0,0 +1,65 @@ +// +// NRUserInfo.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit +import SmartCodable + +class NRUserInfo: NSObject, SmartCodable, NSSecureCoding { + + required override init() { } + + var id: String? + var avator: String? + var coin_left_total: Int? + var is_tourist: Bool? + var family_name: String? + var send_coin_left_total: Int? + var vip_end_time: TimeInterval? + var is_vip: Bool? + var customer_id: String? + + func getNickName() -> String { + if let name = family_name, !name.isEmpty { + return name + } else { + return "Visitor" + } + } + + var totalCoins: Int { + return (coin_left_total ?? 0) + (send_coin_left_total ?? 0) + } + + static var supportsSecureCoding: Bool { + return true + } + + func encode(with coder: NSCoder) { + coder.encode(id, forKey: "id") + coder.encode(customer_id, forKey: "customer_id") + coder.encode(is_tourist, forKey: "is_tourist") + coder.encode(avator, forKey: "avator") + coder.encode(family_name, forKey: "family_name") + coder.encode(coin_left_total, forKey: "coin_left_total") + coder.encode(send_coin_left_total, forKey: "send_coin_left_total") + coder.encode(is_vip, forKey: "is_vip") + coder.encode(vip_end_time, forKey: "vip_end_time") + } + + required init?(coder: NSCoder) { + super.init() + id = coder.decodeObject(of: NSString.self, forKey: "id") as? String + customer_id = coder.decodeObject(of: NSString.self, forKey: "customer_id") as? String + is_tourist = coder.decodeObject(of: NSNumber.self, forKey: "is_tourist")?.boolValue + avator = coder.decodeObject(of: NSString.self, forKey: "avator") as? String + family_name = coder.decodeObject(of: NSString.self, forKey: "family_name") as? String + coin_left_total = coder.decodeObject(of: NSNumber.self, forKey: "coin_left_total")?.intValue + send_coin_left_total = coder.decodeObject(of: NSNumber.self, forKey: "send_coin_left_total")?.intValue + is_vip = coder.decodeObject(of: NSNumber.self, forKey: "is_vip")?.boolValue + vip_end_time = coder.decodeObject(of: NSNumber.self, forKey: "vip_end_time")?.doubleValue + } +} diff --git a/ReaderHive/Libs/NovelTool/NRCoreText.swift b/ReaderHive/Libs/NovelTool/NRCoreText.swift new file mode 100644 index 0000000..a2d90bd --- /dev/null +++ b/ReaderHive/Libs/NovelTool/NRCoreText.swift @@ -0,0 +1,548 @@ +// +// NRCoreText.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/28. +// + +import UIKit +import Darwin + +/// 段落头部双圆角空格 +let DZM_READ_PH_SPACE:String = "  " +let DZM_SPACE_5:CGFloat = 5 + +class NRCoreText: NSObject { + /// 获得 CTFrame + /// + /// - Parameters: + /// - attrString: 内容 + /// - rect: 显示范围 + /// - Returns: CTFrame + @objc class func GetFrameRef(attrString:NSAttributedString, rect:CGRect) ->CTFrame { + + let framesetter = CTFramesetterCreateWithAttributedString(attrString) + + let path = CGPath(rect: rect, transform: nil) + + let frameRef = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil) + + return frameRef + } + + /// 获得内容分页列表 + /// + /// - Parameters: + /// - attrString: 内容 + /// - rect: 显示范围 + /// - Returns: 内容分页列表 + @objc class func GetPageingRanges(attrString:NSAttributedString, rect:CGRect) ->[NSRange] { + + var rangeArray:[NSRange] = [] + + let framesetter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString) + + let path = CGPath(rect: rect, transform: nil) + + var range = CFRangeMake(0, 0) + + var rangeOffset:NSInteger = 0 + + repeat{ + + let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(rangeOffset, 0), path, nil) + + range = CTFrameGetVisibleStringRange(frame) + + rangeArray.append(NSMakeRange(rangeOffset, range.length)) + + rangeOffset += range.length + + }while(range.location + range.length < attrString.length) + + return rangeArray + } + + /// 获得触摸位置文字的Location + /// + /// - Parameters: + /// - point: 触摸位置 + /// - frameRef: 内容 CTFrame + /// - Returns: 触摸位置的Index + @objc class func GetTouchLocation(point:CGPoint, frameRef:CTFrame?) ->CFIndex { + + var location:CFIndex = -1 + + let line = GetTouchLine(point: point, frameRef: frameRef) + + if line != nil { + + location = CTLineGetStringIndexForPosition(line!, point) + } + + return location + } + + /// 获得触摸位置那一行文字的Range + /// + /// - Parameters: + /// - point: 触摸位置 + /// - frameRef: 内容 CTFrame + /// - Returns: 一行的 NSRange + @objc class func GetTouchLineRange(point:CGPoint, frameRef:CTFrame?) ->NSRange { + + let line = GetTouchLine(point: point, frameRef: frameRef) + + return GetLineRange(line: line) + } + + /// 获得触摸位置那一个段落的 NSRange || 一行文字的 NSRange + /// + /// - Parameters: + /// - point: 触摸位置 + /// - frameRef: 内容 CTFrame + /// - content: 内容字符串,传了则获取长按的段落 NSRange,没传则获取一行文字的 NSRange + /// - Returns: 一个段落的 NSRange || 一行文字的 NSRange + @objc class func GetTouchParagraphRange(point:CGPoint, frameRef:CTFrame?, content:String? = nil) ->NSRange { + + let line = GetTouchLine(point: point, frameRef: frameRef) + + var range:NSRange = GetLineRange(line: line) + + // 如果有正文,则获取整个段落的 NSRange + + if (line != nil && content != nil) { + + let lines:[CTLine] = CTFrameGetLines(frameRef!) as! [CTLine] + + let count = lines.count + + let index = lines.index(of: line!)! + + var num = 0 + + var rangeHeader = range + + var rangeFooter = range + + var isHeader = false + + var isFooter = false + + repeat { + + if (!isHeader) { + + let newIndex = index - num + + let line = lines[newIndex] + + rangeHeader = GetLineRange(line: line) + + let headerString = content?.substring(rangeHeader) + + isHeader = headerString?.contains(DZM_READ_PH_SPACE) ?? true + + if (newIndex == 0) { isHeader = true } + } + + if (!isFooter) { + + let newIndex = index + num + + let line = lines[newIndex] + + rangeFooter = GetLineRange(line: line) + + let footerString = content?.substring(rangeFooter) + + isFooter = footerString?.contains("\n") ?? true + + if (newIndex == (count - 1)) { isFooter = true } + } + + num += 1 + + } while (!isHeader || !isFooter) + + range = NSMakeRange(rangeHeader.location, rangeFooter.location + rangeFooter.length - rangeHeader.location) + } + + return range + } + + /// 获得触摸位置在哪一行 + /// + /// - Parameters: + /// - point: 触摸位置 + /// - frameRef: 内容 CTFrame + /// - Returns: CTLine + @objc class func GetTouchLine(point:CGPoint, frameRef:CTFrame?) ->CTLine? { + + var line:CTLine? = nil + + if frameRef == nil { return line } + + let frameRef:CTFrame = frameRef! + + let path:CGPath = CTFrameGetPath(frameRef) + + let bounds:CGRect = path.boundingBox + + let lines:[CTLine] = CTFrameGetLines(frameRef) as! [CTLine] + + if lines.isEmpty { return line } + + let lineCount = lines.count + + let origins = malloc(lineCount * MemoryLayout.size).assumingMemoryBound(to: CGPoint.self) + + CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), origins) + + for i in 0.. [NSRange] { + + var ranges:[NSRange] = [] + + if (frameRef != nil && content != nil) { + + let lines:[CTLine] = CTFrameGetLines(frameRef!) as! [CTLine] + + for line in lines { + + let lintRange = GetLineRange(line: line) + + let lineString = content?.substring(lintRange) + + let isEnd = lineString?.contains("\n") ?? false + + if (isEnd) { ranges.append(lintRange) } + } + } + + return ranges + } + + /// 获取内容所有段落尾部 CGRect + /// + /// - Parameters: + /// - frameRef: 内容 CTFrame + /// - content: 内容字符串,也就是生成 frameRef 的正文内容 + /// - Returns: [CGRect] 传入内容的所有断尾 CGRect + @objc class func GetParagraphEndRects (frameRef:CTFrame?, content:String?) -> [CGRect] { + + var rects:[CGRect] = [] + + let ranges = GetParagraphEndRanges(frameRef: frameRef, content: content) + + for range in ranges { + + let rect = GetRangeRects(range: range, frameRef: frameRef, content: content) + + rects += rect + } + + return rects + } + + /// 通过 range 返回字符串所覆盖的位置 [CGRect] + /// + /// - Parameter range: NSRange + /// - Parameter frameRef: 内容 CTFrame + /// - Parameter content: 内容字符串(有值则可以去除选中每一行区域内的 开头空格 - 尾部换行符 - 所占用的区域,不传默认返回每一行实际占用区域) + /// - Returns: 覆盖位置 + @objc class func GetRangeRects(range:NSRange, frameRef:CTFrame?, content:String? = nil) -> [CGRect] { + + var rects:[CGRect] = [] + + if frameRef == nil { return rects } + + if range.length == 0 || range.location == NSNotFound { return rects } + + let frameRef = frameRef! + + let lines:[CTLine] = CTFrameGetLines(frameRef) as! [CTLine] + + if lines.isEmpty { return rects } + + let lineCount:Int = lines.count + + let origins = malloc(lineCount * MemoryLayout.size).assumingMemoryBound(to: CGPoint.self) + + CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), origins) + + for i in 0.. range.location && lineRange.location < (range.location + range.length) { + + contentRange.location = max(lineRange.location, range.location) + + let end = min(lineRange.location + lineRange.length, range.location + range.length) + + contentRange.length = end - contentRange.location + } + + if contentRange.length > 0 { + + // 去掉 -> 开头空格 - 尾部换行符 - 所占用的区域 + + if content != nil && !content!.isEmpty { + + let tempContent:String = content!.substring(contentRange) + + let spaceRanges:[NSTextCheckingResult] = tempContent.matches("\\s\\s") + + if !spaceRanges.isEmpty { + + let spaceRange = spaceRanges.first!.range + + contentRange = NSMakeRange(contentRange.location + spaceRange.length, contentRange.length - spaceRange.length) + } + + let enterRanges:[NSTextCheckingResult] = tempContent.matches("\\n") + + if !enterRanges.isEmpty { + + let enterRange = enterRanges.first!.range + + contentRange = NSMakeRange(contentRange.location, contentRange.length - enterRange.length) + } + } + + // 正常使用(如果不需要排除段头空格跟段尾换行符可将上面代码删除) + + let xStart:CGFloat = CTLineGetOffsetForStringIndex(line, contentRange.location, nil) + + let xEnd:CGFloat = CTLineGetOffsetForStringIndex(line, contentRange.location + contentRange.length, nil) + + let origin:CGPoint = origins[i] + + var lineAscent:CGFloat = 0 + + var lineDescent:CGFloat = 0 + + var lineLeading:CGFloat = 0 + + CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading) + + let contentRect:CGRect = CGRect(x: origin.x + xStart, y: origin.y - lineDescent, width: fabs(xEnd - xStart), height: lineAscent + lineDescent + lineLeading) + + rects.append(contentRect) + } + } + + free(origins) + + return rects + } + + /// 通过 range 获得合适的 MenuRect + /// + /// - Parameter rects: [CGRect] + /// - Parameter frameRef: 内容 CTFrame + /// - Parameter viewFrame: 目标ViewFrame + /// - Parameter content: 内容字符串 + /// - Returns: MenuRect + @objc class func GetMenuRect(range:NSRange, frameRef:CTFrame?, viewFrame:CGRect, content:String? = nil) ->CGRect { + + let rects = GetRangeRects(range: range, frameRef: frameRef, content: content) + + return GetMenuRect(rects: rects, viewFrame: viewFrame) + } + + /// 通过 [CGRect] 获得合适的 MenuRect + /// + /// - Parameter rects: [CGRect] + /// - Parameter viewFrame: 目标ViewFrame + /// - Returns: MenuRect + @objc class func GetMenuRect(rects:[CGRect], viewFrame:CGRect) ->CGRect { + + var menuRect:CGRect = CGRect.zero + + if rects.isEmpty { return menuRect } + + if rects.count == 1 { + + menuRect = rects.first! + + }else{ + + menuRect = rects.first! + + let count = rects.count + + for i in 1..CGFloat { + + var height:CGFloat = 0 + + if attrString.length > 0 { + + // 注意设置的高度必须大于文本高度 + let maxH:CGFloat = 1000 + + let framesetter = CTFramesetterCreateWithAttributedString(attrString) + + let drawingRect = CGRect(x: 0, y: 0, width: maxW, height: maxH) + + let path = CGPath(rect: drawingRect, transform: nil) + + let frameRef = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil) + + let lines = CTFrameGetLines(frameRef) as! [CTLine] + + var origins:[CGPoint] = Array(repeating: CGPoint.zero, count: lines.count) + + CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), &origins) + + let lineY = origins.last!.y + + var lineAscent:CGFloat = 0 + + var lineDescent:CGFloat = 0 + + var lineLeading:CGFloat = 0 + + CTLineGetTypographicBounds(lines.last!, &lineAscent, &lineDescent, &lineLeading) + + height = maxH - lineY + CGFloat(ceilf(Float(lineDescent))) + } + + return height + } + + /// 获取一行文字的 Range + /// + /// - Parameter line: CTLine + /// - Returns: 一行文字的 Range + @objc class func GetLineRange(line:CTLine?) ->NSRange { + + var range:NSRange = NSMakeRange(NSNotFound, 0) + + if line != nil { + + let lineRange = CTLineGetStringRange(line!) + + range = NSMakeRange(lineRange.location == kCFNotFound ? NSNotFound : lineRange.location, lineRange.length) + } + + return range + } + + /// 获取行高 + /// + /// - Parameter line: CTLine + /// - Returns: 行高 + @objc class func GetLineHeight(frameRef:CTFrame?) ->CGFloat { + + if frameRef == nil { return 0 } + + let frameRef:CTFrame = frameRef! + + let lines:[CTLine] = CTFrameGetLines(frameRef) as! [CTLine] + + if lines.isEmpty { return 0 } + + return GetLineHeight(line: lines.first) + } + + /// 获取行高 + /// + /// - Parameter line: CTLine + /// - Returns: 行高 + @objc class func GetLineHeight(line:CTLine?) ->CGFloat { + + if line == nil { return 0 } + + var lineAscent:CGFloat = 0 + + var lineDescent:CGFloat = 0 + + var lineLeading:CGFloat = 0 + + CTLineGetTypographicBounds(line!, &lineAscent, &lineDescent, &lineLeading) + + return lineAscent + lineDescent + lineLeading + } +} diff --git a/ReaderHive/Libs/NovelTool/NRNovelReadSet.swift b/ReaderHive/Libs/NovelTool/NRNovelReadSet.swift new file mode 100644 index 0000000..c8af230 --- /dev/null +++ b/ReaderHive/Libs/NovelTool/NRNovelReadSet.swift @@ -0,0 +1,68 @@ +// +// NRNovelReadSet.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/27. +// + +import UIKit + +class NRNovelReadSet: NSObject, NSSecureCoding { + enum SpacingType: Int { + case small = 0 + case standard + case large + + var title: String { + switch self { + case .small: + return "Small".localized + case .standard: + return "Standard".localized + case .large: + return "Large".localized + } + } + } + + + required override init() { } + + var theme: NRReadThemeType = .theme1 + var lineSpacingType: SpacingType = .standard + var paragraphSpacingType: SpacingType = .standard + var fontSize: CGFloat = 16 + var isNight: Bool = false + + static var supportsSecureCoding: Bool { + return true + } + + func encode(with coder: NSCoder) { + coder.encode(theme.rawValue, forKey: "theme") + coder.encode(lineSpacingType.rawValue, forKey: "lineSpacingType") + coder.encode(paragraphSpacingType.rawValue, forKey: "paragraphSpacingType") + coder.encode(fontSize, forKey: "fontSize") + coder.encode(isNight, forKey: "isNight") + } + + required init?(coder: NSCoder) { + super.init() + if coder.containsValue(forKey: "theme") { + let val = coder.decodeInteger(forKey: "theme") + theme = NRReadThemeType(rawValue: val) ?? .theme1 + } + if coder.containsValue(forKey: "lineSpacingType") { + let val = coder.decodeInteger(forKey: "lineSpacingType") + lineSpacingType = SpacingType(rawValue: val) ?? .standard + } + if coder.containsValue(forKey: "paragraphSpacingType") { + let val = coder.decodeInteger(forKey: "paragraphSpacingType") + paragraphSpacingType = SpacingType(rawValue: val) ?? .standard + } + + fontSize = CGFloat(coder.decodeObject(of: NSNumber.self, forKey: "fontSize")?.floatValue ?? 16) + + isNight = coder.decodeBool(forKey: "isNight") + } +} diff --git a/ReaderHive/Libs/NovelTool/NRNovelReadSetManager.swift b/ReaderHive/Libs/NovelTool/NRNovelReadSetManager.swift new file mode 100644 index 0000000..253afa9 --- /dev/null +++ b/ReaderHive/Libs/NovelTool/NRNovelReadSetManager.swift @@ -0,0 +1,198 @@ +// +// NRNovelReadSetManager.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/27. +// + +import UIKit + +class NRNovelReadSetManager: NSObject { + + static let manager = NRNovelReadSetManager() + + + private(set) var readSet: NRNovelReadSet = UserDefaults.nr_object(forKey: kNRNovelReadSetDefaultsKey, as: NRNovelReadSet.self) ?? NRNovelReadSet() + + ///动画时间 + let animateDuration = 0.2 + + let topViewHeight: CGFloat = UIScreen.navBarHeight + let bottomViewHeight: CGFloat = UIScreen.safeBottom + 105 + 52 + let contentTopViewHeight: CGFloat = UIScreen.navBarHeight + let contentBottomViewHeight: CGFloat = UIScreen.safeBottom + 30 + ///阅读区域 + var readRect: CGRect { + return .init(x: 16, y: 0, width: UIScreen.width - 32, height: UIScreen.height - contentTopViewHeight - contentBottomViewHeight) + } + + var currentTheme: NRReadTheme { + if isNight { + return nightTheme + } else { + return NRReadTheme.theme(for: readSet.theme) + } + } + + ///夜间模式主题 + lazy var nightTheme: NRReadTheme = { + return NRReadTheme(type: .theme1, backgroundColor: ._24232_B, textColor: ._9_D_9_CA_8, statusBarStyle: .default) + }() + + ///行间距 + var lineSpacing: CGFloat { + switch self.readSet.lineSpacingType { + case .small: + return 8 + case .standard: + return 10 + case .large: + return 12 + } + } + + ///段落间距 + var paragraphSpacing: CGFloat { + switch self.readSet.paragraphSpacingType { + case .small: + return 15 + case .standard: + return 17 + case .large: + return 19 + } + } + + var textColor: UIColor { + return currentTheme.textColor + } + + var textFont: UIFont { + return UIFont.font(ofSize: self.readSet.fontSize, weight: .regular) + } + + var titleFont: UIFont { + return UIFont.font(ofSize: self.readSet.fontSize + 10, weight: .semibold) + } + + var miniFontSize: CGFloat = 12 + var maxFontSize: CGFloat = 24 + + var isNight: Bool { + return readSet.isNight + } + + func updateTheme(type: NRReadThemeType) { + readSet.theme = type + UserDefaults.nr_setObject(readSet, forKey: kNRNovelReadSetDefaultsKey) + NotificationCenter.default.post(name: NRNovelReadSetManager.didChangedThemeNotification, object: nil) + } + + func updateIsNight(isNight: Bool) { + readSet.isNight = isNight + UserDefaults.nr_setObject(readSet, forKey: kNRNovelReadSetDefaultsKey) + NotificationCenter.default.post(name: NRNovelReadSetManager.didChangedThemeNotification, object: nil) + } + + func updateLineSpacing(type: NRNovelReadSet.SpacingType) { + readSet.lineSpacingType = type + UserDefaults.nr_setObject(readSet, forKey: kNRNovelReadSetDefaultsKey) + NotificationCenter.default.post(name: NRNovelReadSetManager.didChangedTextLayoutNotification, object: nil) + } + + func updateParagraphSpacing(type: NRNovelReadSet.SpacingType) { + readSet.paragraphSpacingType = type + UserDefaults.nr_setObject(readSet, forKey: kNRNovelReadSetDefaultsKey) + NotificationCenter.default.post(name: NRNovelReadSetManager.didChangedTextLayoutNotification, object: nil) + } + + func updateFontSize(size: CGFloat) { + readSet.fontSize = size + UserDefaults.nr_setObject(readSet, forKey: kNRNovelReadSetDefaultsKey) + NotificationCenter.default.post(name: NRNovelReadSetManager.didChangedTextLayoutNotification, object: nil) + } + + /// 字体属性 + /// isPaging: 为YES的时候只需要返回跟分页相关的属性即可 (原因:包含UIColor,小数点相关的...不可返回,因为无法进行比较) + func attributes(isTitle:Bool, isPageing:Bool = false) ->[NSAttributedString.Key:Any] { + + // 段落配置 + let paragraphStyle = NSMutableParagraphStyle() + + // 当前行间距(lineSpacing)的倍数(可根据字体大小变化修改倍数) + paragraphStyle.lineHeightMultiple = 1.0 + + if isTitle { + + // 行间距 + paragraphStyle.lineSpacing = 0 + + // 段间距 + paragraphStyle.paragraphSpacing = 0 + + // 对其 + paragraphStyle.alignment = .center + + }else{ + + // 行间距 + paragraphStyle.lineSpacing = lineSpacing + + // 换行模式(避免每页尾部留空白) + paragraphStyle.lineBreakMode = .byCharWrapping +// paragraphStyle.lineBreakMode = .byWordWrapping + + // 段间距 + paragraphStyle.paragraphSpacing = paragraphSpacing + + // 对其 + paragraphStyle.alignment = .justified + } + + let font: UIFont + + if isTitle { + font = self.titleFont + } else { + font = self.textFont + } + + if isPageing { + + return [.font: font, .paragraphStyle: paragraphStyle] + + }else{ + + return [.foregroundColor: textColor, .font: font, .paragraphStyle: paragraphStyle] + } + } + + func emptyAttributes() -> [NSAttributedString.Key:Any] { + // 段落配置 + let paragraphStyle = NSMutableParagraphStyle() + + // 当前行间距(lineSpacing)的倍数(可根据字体大小变化修改倍数) + paragraphStyle.lineHeightMultiple = 1.0 + + // 行间距 + paragraphStyle.lineSpacing = 0 + + // 段间距 + paragraphStyle.paragraphSpacing = 0 + + // 对其 + paragraphStyle.alignment = .center + + return [.font: self.textFont, .paragraphStyle: paragraphStyle] + } +} + + +extension NRNovelReadSetManager { + ///主题色发生变化 + static let didChangedThemeNotification = Notification.Name(rawValue: "NRNovelReadSetManager.didChangedThemeNotification") + + ///文本布局发生变化 (字体,行间距,段落间距等) + static let didChangedTextLayoutNotification = Notification.Name(rawValue: "NRNovelReadSetManager.didChangedTextLayoutNotification") + +} diff --git a/ReaderHive/Libs/NovelTool/NRReadParser.swift b/ReaderHive/Libs/NovelTool/NRReadParser.swift new file mode 100644 index 0000000..2e6dfbb --- /dev/null +++ b/ReaderHive/Libs/NovelTool/NRReadParser.swift @@ -0,0 +1,92 @@ +// +// NRReadParser.swift +// ReaderHive +// +// Created by 长沙鸿瑶 on 2025/11/28. +// + +import UIKit + +class NRReadParser: NSObject { + + // MARK: -- 内容分页 + + /// 内容分页 + /// + /// - Parameters: + /// - attrString: 内容 + /// - rect: 显示范围 + /// - isFirstChapter: 是否为本文章第一个展示章节,如果是则加入书籍首页。(小技巧:如果不需要书籍首页,可不用传,默认就是不带书籍首页) + /// - Returns: 内容分页列表 + @objc class func pageing(attrString:NSAttributedString, rect:CGRect, isFirstChapter:Bool = false) ->[NRReadPageModel] { + + var pageModels:[NRReadPageModel] = [] + +// if isFirstChapter { // 第一页为书籍页面 +// +// let pageModel = NRReadPageModel() +// +// pageModel.range = NSMakeRange(DZM_READ_BOOK_HOME_PAGE, 1) +// +// pageModel.contentSize = DZM_READ_VIEW_RECT.size +// +// pageModels.append(pageModel) +// } + + let ranges = NRCoreText.GetPageingRanges(attrString: attrString, rect: rect) + + if !ranges.isEmpty { + + let count = ranges.count + + for i in 0.. NRReadTheme { + switch type { + case .theme1: + return NRReadTheme(type: type, + backgroundColor: .F_2_EFEE, + textColor: ._666666, + statusBarStyle: .darkContent) + case .theme2: + return NRReadTheme(type: type, + backgroundColor: .FBF_0_D_9, + textColor: ._6_E_5_B_4_B, + statusBarStyle: .darkContent) + case .theme3: + return NRReadTheme(type: type, + backgroundColor: .E_9_F_7_EF, + textColor: ._39524_F, + statusBarStyle: .darkContent) + case .theme4: + return NRReadTheme(type: type, + backgroundColor: .white, + textColor: ._1_A_1_A_1_A, + statusBarStyle: .lightContent) + } + } +} diff --git a/ReaderHive/Libs/Tool/NRTool.swift b/ReaderHive/Libs/Tool/NRTool.swift new file mode 100644 index 0000000..f2882aa --- /dev/null +++ b/ReaderHive/Libs/Tool/NRTool.swift @@ -0,0 +1,56 @@ +// +// NRTool.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/21. +// + +import UIKit + +class NRTool { + + static var appDelegate: AppDelegate? + static var sceneDelegate: SceneDelegate? + + static var windowScene: UIWindowScene? + + + static var keyWindow: UIWindow? { + return windowScene?.keyWindow + } + + static var rootViewController: UIViewController? { + return keyWindow?.rootViewController + } + + ///获得启动图 + static var lanuchViewController: UIViewController? { + let storyboard = UIStoryboard(name: "LaunchScreen", bundle: nil) + let vc = storyboard.instantiateInitialViewController() + return vc + } + + static var topViewController: UIViewController? { + var resultVC: UIViewController? = self.rootViewController + if let rootNav = resultVC as? UINavigationController { + resultVC = rootNav.topViewController + } + + resultVC = self._topViewController(resultVC) + while resultVC?.presentedViewController != nil { + resultVC = self._topViewController(resultVC?.presentedViewController) + } + return resultVC + } + + private static func _topViewController(_ vc: UIViewController?) -> UIViewController? { + if vc is UINavigationController { + return _topViewController((vc as? UINavigationController)?.topViewController) + } else if vc is UITabBarController { + return _topViewController((vc as? UITabBarController)?.selectedViewController) + } else { + return vc + } + } + +} diff --git a/ReaderHive/Libs/WaterfallFlowLayout/NRWaterfallFlowLayout.swift b/ReaderHive/Libs/WaterfallFlowLayout/NRWaterfallFlowLayout.swift new file mode 100644 index 0000000..a195240 --- /dev/null +++ b/ReaderHive/Libs/WaterfallFlowLayout/NRWaterfallFlowLayout.swift @@ -0,0 +1,191 @@ +// +// NRWaterfallFlowLayout.swift +// ReaderHive +// +// Created by 湖北秦九 on 2025/11/24. +// + +import UIKit + +@objc protocol NRWaterfallMutiSectionDelegate: NSObjectProtocol { + // 必选delegate实现 + /// collectionItem高度 + func heightForRowAtIndexPath(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, indexPath: IndexPath, itemWidth: CGFloat) -> CGFloat + + // 可选delegate实现 + /// 每个section 列数(默认2列) + @objc optional func columnNumber(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, section: Int) -> Int + + /// header高度(默认为0) + @objc optional func referenceSizeForHeader(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, section: Int) -> CGSize + + /// footer高度(默认为0) + @objc optional func referenceSizeForFooter(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, section: Int) -> CGSize + + /// 每个section 边距(默认为0) + @objc optional func insetForSection(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, section: Int) -> UIEdgeInsets + + /// 每个section item上下间距(默认为0) + @objc optional func lineSpacing(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, section: Int) -> CGFloat + + /// 每个section item左右间距(默认为0) + @objc optional func interitemSpacing(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, section: Int) -> CGFloat + + /// section头部header与上个section尾部footer间距(默认为0) + @objc optional func spacingWithLastSection(collectionView collection: UICollectionView, layout: NRWaterfallFlowLayout, section: Int) -> CGFloat +} + +class NRWaterfallFlowLayout: UICollectionViewFlowLayout { + weak var delegate: NRWaterfallMutiSectionDelegate? + + private var sectionInsets: UIEdgeInsets = .zero + private var columnCount: Int = 2 + private var lineSpacing: CGFloat = 0 + private var interitemSpacing: CGFloat = 0 + private var headerSize: CGSize = .zero + private var footerSize: CGSize = .zero + + //存放attribute的数组 + private var attrsArray: [UICollectionViewLayoutAttributes] = [] + //存放每个section中各个列的最后一个高度 + private var columnHeights: [CGFloat] = [] + //collectionView的Content的高度 + private var contentHeight: CGFloat = 0 + //记录上个section高度最高一列的高度 + private var lastContentHeight: CGFloat = 0 + //每个section的header与上个section的footer距离 + private var spacingWithLastSection: CGFloat = 0 + + + override func prepare() { + super.prepare() + self.contentHeight = 0 + self.lastContentHeight = 0 + self.spacingWithLastSection = 0 + self.lineSpacing = 0 + self.sectionInsets = .zero + self.headerSize = .zero + self.footerSize = .zero + self.columnHeights.removeAll() + self.attrsArray.removeAll() + + let sectionCount = self.collectionView!.numberOfSections + // 遍历section + for idx in 0.. [UICollectionViewLayoutAttributes]? { + return self.attrsArray + } + + override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + if let column = self.delegate?.columnNumber?(collectionView: self.collectionView!, layout: self, section: indexPath.section) { + self.columnCount = column + } + if let lineSpacing = self.delegate?.lineSpacing?(collectionView: self.collectionView!, layout: self, section: indexPath.section) { + self.lineSpacing = lineSpacing + } + if let interitem = self.delegate?.interitemSpacing?(collectionView: self.collectionView!, layout: self, section: indexPath.section) { + self.interitemSpacing = interitem + } + + let attri = UICollectionViewLayoutAttributes(forCellWith: indexPath) + let weight = self.collectionView!.frame.size.width + let itemSpacing = CGFloat(self.columnCount - 1) * self.interitemSpacing + let allWeight = weight - self.sectionInsets.left - self.sectionInsets.right - itemSpacing + let cellWeight = allWeight / CGFloat(self.columnCount) + let cellHeight: CGFloat = (self.delegate?.heightForRowAtIndexPath(collectionView: self.collectionView!, layout: self, indexPath: indexPath, itemWidth: cellWeight))! + + var tmpMinColumn = 0 + var minColumnHeight = self.columnHeights[0] + for i in 0.. columnH { + minColumnHeight = columnH + tmpMinColumn = i + } + } + let cellX = self.sectionInsets.left + CGFloat(tmpMinColumn) * (cellWeight + self.interitemSpacing) + var cellY: CGFloat = 0 + cellY = minColumnHeight + if cellY != self.lastContentHeight { + cellY += self.lineSpacing + } + + if self.contentHeight < minColumnHeight { + self.contentHeight = minColumnHeight + } + + attri.frame = CGRect(x: cellX, y: cellY, width: cellWeight, height: cellHeight) + self.columnHeights[tmpMinColumn] = attri.frame.maxY + //取最大的 + for i in 0.. UICollectionViewLayoutAttributes? { + let attri = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: elementKind, with: indexPath) + if elementKind == UICollectionView.elementKindSectionHeader { + if let headerSize = self.delegate?.referenceSizeForHeader?(collectionView: self.collectionView!, layout: self, section: indexPath.section) { + self.headerSize = headerSize + } + self.contentHeight += self.spacingWithLastSection + attri.frame = CGRect(x: 0, y: self.contentHeight, width: self.headerSize.width, height: self.headerSize.height) + self.contentHeight += self.headerSize.height + self.contentHeight += self.sectionInsets.top + } else if elementKind == UICollectionView.elementKindSectionFooter { + if let footerSize = self.delegate?.referenceSizeForFooter?(collectionView: self.collectionView!, layout: self, section: indexPath.section) { + self.footerSize = footerSize + } + self.contentHeight += self.sectionInsets.bottom + attri.frame = CGRect(x: 0, y: self.contentHeight, width: self.footerSize.width, height: self.footerSize.height) + self.contentHeight += self.footerSize.height + } + return attri + } + + override var collectionViewContentSize: CGSize { + return CGSize(width: self.collectionView!.frame.size.width, height: self.contentHeight) + } +} diff --git a/ReaderHive/Source/Assets.xcassets/AccentColor.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/AppIcon.appiconset/APP LOGO.jpg b/ReaderHive/Source/Assets.xcassets/AppIcon.appiconset/APP LOGO.jpg new file mode 100644 index 0000000..62ed491 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/AppIcon.appiconset/APP LOGO.jpg differ diff --git a/ReaderHive/Source/Assets.xcassets/AppIcon.appiconset/Contents.json b/ReaderHive/Source/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..c602006 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "APP LOGO.jpg", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#1A1A1A.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#1A1A1A.colorset/Contents.json new file mode 100644 index 0000000..456c6d2 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#1A1A1A.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x1A", + "green" : "0x1A", + "red" : "0x1A" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#24232B.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#24232B.colorset/Contents.json new file mode 100644 index 0000000..21529c2 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#24232B.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2B", + "green" : "0x23", + "red" : "0x24" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#39524F.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#39524F.colorset/Contents.json new file mode 100644 index 0000000..d7e9e18 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#39524F.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x4F", + "green" : "0x52", + "red" : "0x39" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#3C4F9A.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#3C4F9A.colorset/Contents.json new file mode 100644 index 0000000..bcdb719 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#3C4F9A.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x9A", + "green" : "0x4F", + "red" : "0x3C" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#666666.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#666666.colorset/Contents.json new file mode 100644 index 0000000..a310fa4 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#666666.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x66", + "green" : "0x66", + "red" : "0x66" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#6E5B4B.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#6E5B4B.colorset/Contents.json new file mode 100644 index 0000000..8a182dc --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#6E5B4B.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x4B", + "green" : "0x5B", + "red" : "0x6E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#999999.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#999999.colorset/Contents.json new file mode 100644 index 0000000..867360e --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#999999.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x99", + "green" : "0x99", + "red" : "0x99" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#9D9CA8.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#9D9CA8.colorset/Contents.json new file mode 100644 index 0000000..6083211 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#9D9CA8.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xA8", + "green" : "0x9C", + "red" : "0x9D" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#D5E8F4.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#D5E8F4.colorset/Contents.json new file mode 100644 index 0000000..b60bab2 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#D5E8F4.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF4", + "green" : "0xE8", + "red" : "0xD5" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#D7E5CB.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#D7E5CB.colorset/Contents.json new file mode 100644 index 0000000..729a61d --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#D7E5CB.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xCB", + "green" : "0xE5", + "red" : "0xD7" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#E0E0E0.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#E0E0E0.colorset/Contents.json new file mode 100644 index 0000000..e7d0ceb --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#E0E0E0.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE0", + "green" : "0xE0", + "red" : "0xE0" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#E9F7EF.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#E9F7EF.colorset/Contents.json new file mode 100644 index 0000000..85f25cf --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#E9F7EF.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xEF", + "green" : "0xF7", + "red" : "0xE9" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#F2EFEE.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#F2EFEE.colorset/Contents.json new file mode 100644 index 0000000..4418f2b --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#F2EFEE.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xEE", + "green" : "0xEF", + "red" : "0xF2" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#F3912F.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#F3912F.colorset/Contents.json new file mode 100644 index 0000000..1d9de5b --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#F3912F.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2F", + "green" : "0x91", + "red" : "0xF3" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#F6F6F6.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#F6F6F6.colorset/Contents.json new file mode 100644 index 0000000..4522cee --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#F6F6F6.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF6", + "green" : "0xF6", + "red" : "0xF6" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#F8F8F8.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#F8F8F8.colorset/Contents.json new file mode 100644 index 0000000..6ced91c --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#F8F8F8.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF8", + "green" : "0xF8", + "red" : "0xF8" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#F9710D.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#F9710D.colorset/Contents.json new file mode 100644 index 0000000..a5fb95a --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#F9710D.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x0D", + "green" : "0x71", + "red" : "0xF9" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#F9F1E6.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#F9F1E6.colorset/Contents.json new file mode 100644 index 0000000..ede3f07 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#F9F1E6.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE6", + "green" : "0xF1", + "red" : "0xF9" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#FA9B1F.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#FA9B1F.colorset/Contents.json new file mode 100644 index 0000000..c446c39 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#FA9B1F.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x1F", + "green" : "0x9B", + "red" : "0xFA" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#FBF0D9.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#FBF0D9.colorset/Contents.json new file mode 100644 index 0000000..436bb92 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#FBF0D9.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xD9", + "green" : "0xF0", + "red" : "0xFB" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#FF4A4A.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#FF4A4A.colorset/Contents.json new file mode 100644 index 0000000..4336e40 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#FF4A4A.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x4A", + "green" : "0x4A", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#FFC300.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#FFC300.colorset/Contents.json new file mode 100644 index 0000000..b66d060 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#FFC300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0xC3", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#FFC64A.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#FFC64A.colorset/Contents.json new file mode 100644 index 0000000..759d6bd --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#FFC64A.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x4A", + "green" : "0xC6", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#FFC982.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#FFC982.colorset/Contents.json new file mode 100644 index 0000000..d0434e3 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#FFC982.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x82", + "green" : "0xC9", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#FFEFD4.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#FFEFD4.colorset/Contents.json new file mode 100644 index 0000000..ca389ee --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#FFEFD4.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xD4", + "green" : "0xEF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#FFEFDF.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#FFEFDF.colorset/Contents.json new file mode 100644 index 0000000..fcfda2d --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#FFEFDF.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xDF", + "green" : "0xEF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#FFF7BE.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#FFF7BE.colorset/Contents.json new file mode 100644 index 0000000..afe4a43 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#FFF7BE.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xBE", + "green" : "0xF7", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/#FFFCE3.colorset/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/#FFFCE3.colorset/Contents.json new file mode 100644 index 0000000..95e7354 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/#FFFCE3.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE3", + "green" : "0xFC", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Color/Contents.json b/ReaderHive/Source/Assets.xcassets/Color/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Color/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Contents.json b/ReaderHive/Source/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/+_icon_01.imageset/Add@2x.png b/ReaderHive/Source/Assets.xcassets/Image/+_icon_01.imageset/Add@2x.png new file mode 100644 index 0000000..083a558 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/+_icon_01.imageset/Add@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/+_icon_01.imageset/Add@3x.png b/ReaderHive/Source/Assets.xcassets/Image/+_icon_01.imageset/Add@3x.png new file mode 100644 index 0000000..9ff4797 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/+_icon_01.imageset/Add@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/+_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/+_icon_01.imageset/Contents.json new file mode 100644 index 0000000..c0b9d7c --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/+_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Add@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Add@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/Progress-handle.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/Progress-handle.imageset/Contents.json new file mode 100644 index 0000000..db385b1 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/Progress-handle.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Progress-handle@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Progress-handle@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/Progress-handle.imageset/Progress-handle@2x.png b/ReaderHive/Source/Assets.xcassets/Image/Progress-handle.imageset/Progress-handle@2x.png new file mode 100644 index 0000000..8011b33 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/Progress-handle.imageset/Progress-handle@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/Progress-handle.imageset/Progress-handle@3x.png b/ReaderHive/Source/Assets.xcassets/Image/Progress-handle.imageset/Progress-handle@3x.png new file mode 100644 index 0000000..acf6029 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/Progress-handle.imageset/Progress-handle@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/about_icon_01.imageset/About@2x.png b/ReaderHive/Source/Assets.xcassets/Image/about_icon_01.imageset/About@2x.png new file mode 100644 index 0000000..756c0a0 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/about_icon_01.imageset/About@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/about_icon_01.imageset/About@3x.png b/ReaderHive/Source/Assets.xcassets/Image/about_icon_01.imageset/About@3x.png new file mode 100644 index 0000000..1f24b2f Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/about_icon_01.imageset/About@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/about_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/about_icon_01.imageset/Contents.json new file mode 100644 index 0000000..812deec --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/about_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "About@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "About@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/alert_bg_image_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/alert_bg_image_01.imageset/Contents.json new file mode 100644 index 0000000..3710508 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/alert_bg_image_01.imageset/Contents.json @@ -0,0 +1,44 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Default@2x.png", + "idiom" : "universal", + "resizing" : { + "cap-insets" : { + "bottom" : 48, + "top" : 289 + }, + "center" : { + "height" : 1, + "mode" : "tile" + }, + "mode" : "3-part-vertical" + }, + "scale" : "2x" + }, + { + "filename" : "Default@3x.png", + "idiom" : "universal", + "resizing" : { + "cap-insets" : { + "bottom" : 85, + "top" : 414 + }, + "center" : { + "height" : 1, + "mode" : "tile" + }, + "mode" : "3-part-vertical" + }, + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/alert_bg_image_01.imageset/Default@2x.png b/ReaderHive/Source/Assets.xcassets/Image/alert_bg_image_01.imageset/Default@2x.png new file mode 100644 index 0000000..1f2b904 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/alert_bg_image_01.imageset/Default@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/alert_bg_image_01.imageset/Default@3x.png b/ReaderHive/Source/Assets.xcassets/Image/alert_bg_image_01.imageset/Default@3x.png new file mode 100644 index 0000000..106d369 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/alert_bg_image_01.imageset/Default@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/alert_top_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/alert_top_icon_01.imageset/Contents.json new file mode 100644 index 0000000..2ac5f7d --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/alert_top_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "图片@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "图片@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/alert_top_icon_01.imageset/图片@2x.png b/ReaderHive/Source/Assets.xcassets/Image/alert_top_icon_01.imageset/图片@2x.png new file mode 100644 index 0000000..53aac8a Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/alert_top_icon_01.imageset/图片@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/alert_top_icon_01.imageset/图片@3x.png b/ReaderHive/Source/Assets.xcassets/Image/alert_top_icon_01.imageset/图片@3x.png new file mode 100644 index 0000000..c9f358d Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/alert_top_icon_01.imageset/图片@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_bown_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/arrow_bown_icon_01.imageset/Contents.json new file mode 100644 index 0000000..b7a2807 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/arrow_bown_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Right (右)@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Right (右)@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_bown_icon_01.imageset/Right (右)@2x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_bown_icon_01.imageset/Right (右)@2x.png new file mode 100644 index 0000000..ef7cc43 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_bown_icon_01.imageset/Right (右)@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_bown_icon_01.imageset/Right (右)@3x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_bown_icon_01.imageset/Right (右)@3x.png new file mode 100644 index 0000000..04dd6a9 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_bown_icon_01.imageset/Right (右)@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_01.imageset/Contents.json new file mode 100644 index 0000000..b55f766 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_01.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "return@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "return@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_01.imageset/return@2x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_01.imageset/return@2x.png new file mode 100644 index 0000000..7c3352d Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_01.imageset/return@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_01.imageset/return@3x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_01.imageset/return@3x.png new file mode 100644 index 0000000..887fdee Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_01.imageset/return@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_02.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_02.imageset/Contents.json new file mode 100644 index 0000000..b55f766 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_02.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "return@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "return@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_02.imageset/return@2x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_02.imageset/return@2x.png new file mode 100644 index 0000000..0136c95 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_02.imageset/return@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_02.imageset/return@3x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_02.imageset/return@3x.png new file mode 100644 index 0000000..fe09c91 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_02.imageset/return@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_03.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_03.imageset/Contents.json new file mode 100644 index 0000000..dff0e1f --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_03.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "return@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "return@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_03.imageset/return@2x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_03.imageset/return@2x.png new file mode 100644 index 0000000..76205b7 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_03.imageset/return@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_03.imageset/return@3x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_03.imageset/return@3x.png new file mode 100644 index 0000000..846bbe0 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_03.imageset/return@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_04.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_04.imageset/Contents.json new file mode 100644 index 0000000..dff0e1f --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_04.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "return@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "return@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_04.imageset/return@2x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_04.imageset/return@2x.png new file mode 100644 index 0000000..76205b7 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_04.imageset/return@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_04.imageset/return@3x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_04.imageset/return@3x.png new file mode 100644 index 0000000..846bbe0 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_04.imageset/return@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_05.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_05.imageset/Contents.json new file mode 100644 index 0000000..b55f766 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_05.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "return@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "return@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_05.imageset/return@2x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_05.imageset/return@2x.png new file mode 100644 index 0000000..8db1d9d Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_05.imageset/return@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_05.imageset/return@3x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_05.imageset/return@3x.png new file mode 100644 index 0000000..c1dea78 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_left_icon_05.imageset/return@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_01.imageset/Contents.json new file mode 100644 index 0000000..b7a2807 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Right (右)@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Right (右)@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_01.imageset/Right (右)@2x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_01.imageset/Right (右)@2x.png new file mode 100644 index 0000000..b8a98d7 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_01.imageset/Right (右)@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_01.imageset/Right (右)@3x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_01.imageset/Right (右)@3x.png new file mode 100644 index 0000000..12c1a12 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_01.imageset/Right (右)@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_02.imageset/Arrow@2x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_02.imageset/Arrow@2x.png new file mode 100644 index 0000000..27316c2 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_02.imageset/Arrow@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_02.imageset/Arrow@3x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_02.imageset/Arrow@3x.png new file mode 100644 index 0000000..3f2f9d1 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_02.imageset/Arrow@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_02.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_02.imageset/Contents.json new file mode 100644 index 0000000..0c42e29 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Arrow@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Arrow@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_03.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_03.imageset/Contents.json new file mode 100644 index 0000000..b7a2807 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_03.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Right (右)@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Right (右)@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_03.imageset/Right (右)@2x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_03.imageset/Right (右)@2x.png new file mode 100644 index 0000000..b8a98d7 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_03.imageset/Right (右)@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_03.imageset/Right (右)@3x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_03.imageset/Right (右)@3x.png new file mode 100644 index 0000000..12c1a12 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_03.imageset/Right (右)@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_04.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_04.imageset/Contents.json new file mode 100644 index 0000000..b7a2807 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_04.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Right (右)@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Right (右)@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_04.imageset/Right (右)@2x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_04.imageset/Right (右)@2x.png new file mode 100644 index 0000000..c04847d Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_04.imageset/Right (右)@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_04.imageset/Right (右)@3x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_04.imageset/Right (右)@3x.png new file mode 100644 index 0000000..056fd49 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_04.imageset/Right (右)@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_05.imageset/Arrow@2x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_05.imageset/Arrow@2x.png new file mode 100644 index 0000000..0ec02b0 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_05.imageset/Arrow@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_05.imageset/Arrow@3x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_05.imageset/Arrow@3x.png new file mode 100644 index 0000000..628edfd Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_05.imageset/Arrow@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_05.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_05.imageset/Contents.json new file mode 100644 index 0000000..0c42e29 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_05.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Arrow@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Arrow@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_06.imageset/Arrow@2x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_06.imageset/Arrow@2x.png new file mode 100644 index 0000000..09cfc7b Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_06.imageset/Arrow@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_06.imageset/Arrow@3x.png b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_06.imageset/Arrow@3x.png new file mode 100644 index 0000000..0d499d7 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_06.imageset/Arrow@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_06.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_06.imageset/Contents.json new file mode 100644 index 0000000..0c42e29 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/arrow_right_icon_06.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Arrow@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Arrow@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/battery_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/battery_icon_01.imageset/Contents.json new file mode 100644 index 0000000..2c24563 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/battery_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "电量@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "电量@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/battery_icon_01.imageset/电量@2x.png b/ReaderHive/Source/Assets.xcassets/Image/battery_icon_01.imageset/电量@2x.png new file mode 100644 index 0000000..c94301e Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/battery_icon_01.imageset/电量@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/battery_icon_01.imageset/电量@3x.png b/ReaderHive/Source/Assets.xcassets/Image/battery_icon_01.imageset/电量@3x.png new file mode 100644 index 0000000..5987331 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/battery_icon_01.imageset/电量@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/battery_icon_02.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/battery_icon_02.imageset/Contents.json new file mode 100644 index 0000000..2c24563 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/battery_icon_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "电量@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "电量@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/battery_icon_02.imageset/电量@2x.png b/ReaderHive/Source/Assets.xcassets/Image/battery_icon_02.imageset/电量@2x.png new file mode 100644 index 0000000..7a68f3e Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/battery_icon_02.imageset/电量@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/battery_icon_02.imageset/电量@3x.png b/ReaderHive/Source/Assets.xcassets/Image/battery_icon_02.imageset/电量@3x.png new file mode 100644 index 0000000..fa61189 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/battery_icon_02.imageset/电量@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/bg_image_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/bg_image_01.imageset/Contents.json new file mode 100644 index 0000000..28dd669 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/bg_image_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : " Page@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : " Page@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/bg_image_01.imageset/ Page@2x.png b/ReaderHive/Source/Assets.xcassets/Image/bg_image_01.imageset/ Page@2x.png new file mode 100644 index 0000000..41461de Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/bg_image_01.imageset/ Page@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/bg_image_01.imageset/ Page@3x.png b/ReaderHive/Source/Assets.xcassets/Image/bg_image_01.imageset/ Page@3x.png new file mode 100644 index 0000000..6241611 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/bg_image_01.imageset/ Page@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/bg_image_02.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/bg_image_02.imageset/Contents.json new file mode 100644 index 0000000..70cad87 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/bg_image_02.imageset/Contents.json @@ -0,0 +1,50 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Rectangle 1215@2x.png", + "idiom" : "universal", + "resizing" : { + "cap-insets" : { + "bottom" : 42, + "left" : 88, + "right" : 174, + "top" : 269 + }, + "center" : { + "height" : 1, + "mode" : "tile", + "width" : 1 + }, + "mode" : "9-part" + }, + "scale" : "2x" + }, + { + "filename" : "Rectangle 1215@3x.png", + "idiom" : "universal", + "resizing" : { + "cap-insets" : { + "bottom" : 69, + "left" : 91, + "right" : 87, + "top" : 313 + }, + "center" : { + "height" : 1, + "mode" : "tile", + "width" : 1 + }, + "mode" : "9-part" + }, + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/bg_image_02.imageset/Rectangle 1215@2x.png b/ReaderHive/Source/Assets.xcassets/Image/bg_image_02.imageset/Rectangle 1215@2x.png new file mode 100644 index 0000000..496dd00 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/bg_image_02.imageset/Rectangle 1215@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/bg_image_02.imageset/Rectangle 1215@3x.png b/ReaderHive/Source/Assets.xcassets/Image/bg_image_02.imageset/Rectangle 1215@3x.png new file mode 100644 index 0000000..a6021ab Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/bg_image_02.imageset/Rectangle 1215@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/bg_image_03.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/bg_image_03.imageset/Contents.json new file mode 100644 index 0000000..3abe1f5 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/bg_image_03.imageset/Contents.json @@ -0,0 +1,44 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Rectangle 1216@2x.png", + "idiom" : "universal", + "resizing" : { + "cap-insets" : { + "left" : 216, + "right" : 49 + }, + "center" : { + "mode" : "tile", + "width" : 1 + }, + "mode" : "3-part-horizontal" + }, + "scale" : "2x" + }, + { + "filename" : "Rectangle 1216@3x.png", + "idiom" : "universal", + "resizing" : { + "cap-insets" : { + "left" : 335, + "right" : 393 + }, + "center" : { + "mode" : "tile", + "width" : 1 + }, + "mode" : "3-part-horizontal" + }, + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/bg_image_03.imageset/Rectangle 1216@2x.png b/ReaderHive/Source/Assets.xcassets/Image/bg_image_03.imageset/Rectangle 1216@2x.png new file mode 100644 index 0000000..3d744c1 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/bg_image_03.imageset/Rectangle 1216@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/bg_image_03.imageset/Rectangle 1216@3x.png b/ReaderHive/Source/Assets.xcassets/Image/bg_image_03.imageset/Rectangle 1216@3x.png new file mode 100644 index 0000000..54e549e Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/bg_image_03.imageset/Rectangle 1216@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/bg_image_04.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/bg_image_04.imageset/Contents.json new file mode 100644 index 0000000..2accd77 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/bg_image_04.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "阅读完成页背景@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "阅读完成页背景@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/bg_image_04.imageset/阅读完成页背景@2x.png b/ReaderHive/Source/Assets.xcassets/Image/bg_image_04.imageset/阅读完成页背景@2x.png new file mode 100644 index 0000000..ab0841b Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/bg_image_04.imageset/阅读完成页背景@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/bg_image_04.imageset/阅读完成页背景@3x.png b/ReaderHive/Source/Assets.xcassets/Image/bg_image_04.imageset/阅读完成页背景@3x.png new file mode 100644 index 0000000..020fd8b Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/bg_image_04.imageset/阅读完成页背景@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/catalog_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/catalog_icon_01.imageset/Contents.json new file mode 100644 index 0000000..05a98b9 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/catalog_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "目录@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "目录@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/catalog_icon_01.imageset/目录@2x.png b/ReaderHive/Source/Assets.xcassets/Image/catalog_icon_01.imageset/目录@2x.png new file mode 100644 index 0000000..1c42bd5 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/catalog_icon_01.imageset/目录@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/catalog_icon_01.imageset/目录@3x.png b/ReaderHive/Source/Assets.xcassets/Image/catalog_icon_01.imageset/目录@3x.png new file mode 100644 index 0000000..574288a Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/catalog_icon_01.imageset/目录@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01.imageset/Checked@2x.png b/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01.imageset/Checked@2x.png new file mode 100644 index 0000000..047c956 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01.imageset/Checked@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01.imageset/Checked@3x.png b/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01.imageset/Checked@3x.png new file mode 100644 index 0000000..b693f7a Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01.imageset/Checked@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01.imageset/Contents.json new file mode 100644 index 0000000..de4f2f2 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Checked@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Checked@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01_selected.imageset/Checked@2x.png b/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01_selected.imageset/Checked@2x.png new file mode 100644 index 0000000..c210438 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01_selected.imageset/Checked@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01_selected.imageset/Checked@3x.png b/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01_selected.imageset/Checked@3x.png new file mode 100644 index 0000000..1aa35b3 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01_selected.imageset/Checked@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01_selected.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01_selected.imageset/Contents.json new file mode 100644 index 0000000..de4f2f2 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/checked_icon_01_selected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Checked@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Checked@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/close_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/close_icon_01.imageset/Contents.json new file mode 100644 index 0000000..b58aab8 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/close_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "关闭按钮@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "关闭按钮@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/close_icon_01.imageset/关闭按钮@2x.png b/ReaderHive/Source/Assets.xcassets/Image/close_icon_01.imageset/关闭按钮@2x.png new file mode 100644 index 0000000..e87824d Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/close_icon_01.imageset/关闭按钮@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/close_icon_01.imageset/关闭按钮@3x.png b/ReaderHive/Source/Assets.xcassets/Image/close_icon_01.imageset/关闭按钮@3x.png new file mode 100644 index 0000000..7ffe8cb Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/close_icon_01.imageset/关闭按钮@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/coins_icon_01.imageset/Coins@2x.png b/ReaderHive/Source/Assets.xcassets/Image/coins_icon_01.imageset/Coins@2x.png new file mode 100644 index 0000000..e3472ed Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/coins_icon_01.imageset/Coins@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/coins_icon_01.imageset/Coins@3x.png b/ReaderHive/Source/Assets.xcassets/Image/coins_icon_01.imageset/Coins@3x.png new file mode 100644 index 0000000..32a143d Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/coins_icon_01.imageset/Coins@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/coins_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/coins_icon_01.imageset/Contents.json new file mode 100644 index 0000000..21da17f --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/coins_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Coins@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Coins@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01.imageset/Contents.json new file mode 100644 index 0000000..8afd85a --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 2072750560@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 2072750560@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01.imageset/Frame 2072750560@2x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01.imageset/Frame 2072750560@2x.png new file mode 100644 index 0000000..e5e2b9d Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01.imageset/Frame 2072750560@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01.imageset/Frame 2072750560@3x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01.imageset/Frame 2072750560@3x.png new file mode 100644 index 0000000..e824302 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01.imageset/Frame 2072750560@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected 1.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected 1.imageset/Contents.json new file mode 100644 index 0000000..d10e82c --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected 1.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 2072750562@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 2072750562@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected 1.imageset/Frame 2072750562@2x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected 1.imageset/Frame 2072750562@2x.png new file mode 100644 index 0000000..f03fd45 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected 1.imageset/Frame 2072750562@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected 1.imageset/Frame 2072750562@3x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected 1.imageset/Frame 2072750562@3x.png new file mode 100644 index 0000000..ef41cd1 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected 1.imageset/Frame 2072750562@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected.imageset/Contents.json new file mode 100644 index 0000000..d10e82c --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 2072750562@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 2072750562@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected.imageset/Frame 2072750562@2x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected.imageset/Frame 2072750562@2x.png new file mode 100644 index 0000000..f03fd45 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected.imageset/Frame 2072750562@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected.imageset/Frame 2072750562@3x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected.imageset/Frame 2072750562@3x.png new file mode 100644 index 0000000..ef41cd1 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_01_selected.imageset/Frame 2072750562@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_02.imageset/Collect@2x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_02.imageset/Collect@2x.png new file mode 100644 index 0000000..37a190b Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_02.imageset/Collect@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_02.imageset/Collect@3x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_02.imageset/Collect@3x.png new file mode 100644 index 0000000..7c14819 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_02.imageset/Collect@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_02.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_02.imageset/Contents.json new file mode 100644 index 0000000..d9aeba1 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Collect@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Collect@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03.imageset/Collect@2x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03.imageset/Collect@2x.png new file mode 100644 index 0000000..ebc0fe6 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03.imageset/Collect@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03.imageset/Collect@3x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03.imageset/Collect@3x.png new file mode 100644 index 0000000..9656a81 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03.imageset/Collect@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03.imageset/Contents.json new file mode 100644 index 0000000..d9aeba1 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Collect@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Collect@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03_selected.imageset/Collect@2x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03_selected.imageset/Collect@2x.png new file mode 100644 index 0000000..429eeed Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03_selected.imageset/Collect@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03_selected.imageset/Collect@3x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03_selected.imageset/Collect@3x.png new file mode 100644 index 0000000..7af5c06 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03_selected.imageset/Collect@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03_selected.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03_selected.imageset/Contents.json new file mode 100644 index 0000000..d9aeba1 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_03_selected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Collect@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Collect@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04.imageset/Collect@2x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04.imageset/Collect@2x.png new file mode 100644 index 0000000..5168b2b Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04.imageset/Collect@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04.imageset/Collect@3x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04.imageset/Collect@3x.png new file mode 100644 index 0000000..12db51d Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04.imageset/Collect@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04.imageset/Contents.json new file mode 100644 index 0000000..d9aeba1 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Collect@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Collect@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04_selected.imageset/Collect@2x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04_selected.imageset/Collect@2x.png new file mode 100644 index 0000000..429eeed Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04_selected.imageset/Collect@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04_selected.imageset/Collect@3x.png b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04_selected.imageset/Collect@3x.png new file mode 100644 index 0000000..7af5c06 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04_selected.imageset/Collect@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04_selected.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04_selected.imageset/Contents.json new file mode 100644 index 0000000..d9aeba1 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/collect_icon_04_selected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Collect@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Collect@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/collected_icon_01.imageset/Collect@2x.png b/ReaderHive/Source/Assets.xcassets/Image/collected_icon_01.imageset/Collect@2x.png new file mode 100644 index 0000000..ab63cfd Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collected_icon_01.imageset/Collect@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collected_icon_01.imageset/Collect@3x.png b/ReaderHive/Source/Assets.xcassets/Image/collected_icon_01.imageset/Collect@3x.png new file mode 100644 index 0000000..2d794c6 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/collected_icon_01.imageset/Collect@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/collected_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/collected_icon_01.imageset/Contents.json new file mode 100644 index 0000000..d9aeba1 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/collected_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Collect@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Collect@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/copy_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/copy_icon_01.imageset/Contents.json new file mode 100644 index 0000000..a99b77f --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/copy_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Copy (复制)@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Copy (复制)@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/copy_icon_01.imageset/Copy (复制)@2x.png b/ReaderHive/Source/Assets.xcassets/Image/copy_icon_01.imageset/Copy (复制)@2x.png new file mode 100644 index 0000000..d309bb0 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/copy_icon_01.imageset/Copy (复制)@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/copy_icon_01.imageset/Copy (复制)@3x.png b/ReaderHive/Source/Assets.xcassets/Image/copy_icon_01.imageset/Copy (复制)@3x.png new file mode 100644 index 0000000..1d6772f Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/copy_icon_01.imageset/Copy (复制)@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/decorate_image_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/decorate_image_01.imageset/Contents.json new file mode 100644 index 0000000..f180462 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/decorate_image_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "阅读完成徽章@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "阅读完成徽章@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/decorate_image_01.imageset/阅读完成徽章@2x.png b/ReaderHive/Source/Assets.xcassets/Image/decorate_image_01.imageset/阅读完成徽章@2x.png new file mode 100644 index 0000000..411096a Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/decorate_image_01.imageset/阅读完成徽章@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/decorate_image_01.imageset/阅读完成徽章@3x.png b/ReaderHive/Source/Assets.xcassets/Image/decorate_image_01.imageset/阅读完成徽章@3x.png new file mode 100644 index 0000000..685997f Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/decorate_image_01.imageset/阅读完成徽章@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/delete_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/delete_icon_01.imageset/Contents.json new file mode 100644 index 0000000..6b57fb5 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/delete_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "delete@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "delete@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/delete_icon_01.imageset/delete@2x.png b/ReaderHive/Source/Assets.xcassets/Image/delete_icon_01.imageset/delete@2x.png new file mode 100644 index 0000000..88b349e Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/delete_icon_01.imageset/delete@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/delete_icon_01.imageset/delete@3x.png b/ReaderHive/Source/Assets.xcassets/Image/delete_icon_01.imageset/delete@3x.png new file mode 100644 index 0000000..706d89e Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/delete_icon_01.imageset/delete@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/delete_icon_02.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/delete_icon_02.imageset/Contents.json new file mode 100644 index 0000000..6b57fb5 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/delete_icon_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "delete@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "delete@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/delete_icon_02.imageset/delete@2x.png b/ReaderHive/Source/Assets.xcassets/Image/delete_icon_02.imageset/delete@2x.png new file mode 100644 index 0000000..066af7b Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/delete_icon_02.imageset/delete@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/delete_icon_02.imageset/delete@3x.png b/ReaderHive/Source/Assets.xcassets/Image/delete_icon_02.imageset/delete@3x.png new file mode 100644 index 0000000..cfdb6e7 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/delete_icon_02.imageset/delete@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/done_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/done_icon_01.imageset/Contents.json new file mode 100644 index 0000000..aad72bf --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/done_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Done@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Done@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/done_icon_01.imageset/Done@2x.png b/ReaderHive/Source/Assets.xcassets/Image/done_icon_01.imageset/Done@2x.png new file mode 100644 index 0000000..f04f63d Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/done_icon_01.imageset/Done@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/done_icon_01.imageset/Done@3x.png b/ReaderHive/Source/Assets.xcassets/Image/done_icon_01.imageset/Done@3x.png new file mode 100644 index 0000000..2c8eac9 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/done_icon_01.imageset/Done@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/done_icon_02.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/done_icon_02.imageset/Contents.json new file mode 100644 index 0000000..aad72bf --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/done_icon_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Done@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Done@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/done_icon_02.imageset/Done@2x.png b/ReaderHive/Source/Assets.xcassets/Image/done_icon_02.imageset/Done@2x.png new file mode 100644 index 0000000..f44e118 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/done_icon_02.imageset/Done@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/done_icon_02.imageset/Done@3x.png b/ReaderHive/Source/Assets.xcassets/Image/done_icon_02.imageset/Done@3x.png new file mode 100644 index 0000000..0cefd25 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/done_icon_02.imageset/Done@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/done_icon_03.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/done_icon_03.imageset/Contents.json new file mode 100644 index 0000000..aad72bf --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/done_icon_03.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Done@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Done@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/done_icon_03.imageset/Done@2x.png b/ReaderHive/Source/Assets.xcassets/Image/done_icon_03.imageset/Done@2x.png new file mode 100644 index 0000000..957e9de Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/done_icon_03.imageset/Done@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/done_icon_03.imageset/Done@3x.png b/ReaderHive/Source/Assets.xcassets/Image/done_icon_03.imageset/Done@3x.png new file mode 100644 index 0000000..fcbddbc Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/done_icon_03.imageset/Done@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/edit_done_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/edit_done_icon_01.imageset/Contents.json new file mode 100644 index 0000000..aad72bf --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/edit_done_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Done@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Done@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/edit_done_icon_01.imageset/Done@2x.png b/ReaderHive/Source/Assets.xcassets/Image/edit_done_icon_01.imageset/Done@2x.png new file mode 100644 index 0000000..f32db41 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/edit_done_icon_01.imageset/Done@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/edit_done_icon_01.imageset/Done@3x.png b/ReaderHive/Source/Assets.xcassets/Image/edit_done_icon_01.imageset/Done@3x.png new file mode 100644 index 0000000..743a111 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/edit_done_icon_01.imageset/Done@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/edit_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/edit_icon_01.imageset/Contents.json new file mode 100644 index 0000000..39f34d5 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/edit_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Edit@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Edit@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/edit_icon_01.imageset/Edit@2x.png b/ReaderHive/Source/Assets.xcassets/Image/edit_icon_01.imageset/Edit@2x.png new file mode 100644 index 0000000..9c3a6a3 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/edit_icon_01.imageset/Edit@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/edit_icon_01.imageset/Edit@3x.png b/ReaderHive/Source/Assets.xcassets/Image/edit_icon_01.imageset/Edit@3x.png new file mode 100644 index 0000000..59d1935 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/edit_icon_01.imageset/Edit@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/empty_image_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/empty_image_01.imageset/Contents.json new file mode 100644 index 0000000..4991ac3 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/empty_image_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "undraw_no-data_ig65 (1)@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "undraw_no-data_ig65 (1)@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/empty_image_01.imageset/undraw_no-data_ig65 (1)@2x.png b/ReaderHive/Source/Assets.xcassets/Image/empty_image_01.imageset/undraw_no-data_ig65 (1)@2x.png new file mode 100644 index 0000000..56b77e5 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/empty_image_01.imageset/undraw_no-data_ig65 (1)@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/empty_image_01.imageset/undraw_no-data_ig65 (1)@3x.png b/ReaderHive/Source/Assets.xcassets/Image/empty_image_01.imageset/undraw_no-data_ig65 (1)@3x.png new file mode 100644 index 0000000..c7935d8 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/empty_image_01.imageset/undraw_no-data_ig65 (1)@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01.imageset/Contents.json new file mode 100644 index 0000000..2e5b2b6 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Ticket@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Ticket@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01.imageset/Ticket@2x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01.imageset/Ticket@2x.png new file mode 100644 index 0000000..8dbe523 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01.imageset/Ticket@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01.imageset/Ticket@3x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01.imageset/Ticket@3x.png new file mode 100644 index 0000000..85b2ae8 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01.imageset/Ticket@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01_selected.imageset/Collect@2x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01_selected.imageset/Collect@2x.png new file mode 100644 index 0000000..d7be66b Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01_selected.imageset/Collect@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01_selected.imageset/Collect@3x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01_selected.imageset/Collect@3x.png new file mode 100644 index 0000000..0c965d5 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01_selected.imageset/Collect@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01_selected.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01_selected.imageset/Contents.json new file mode 100644 index 0000000..d9aeba1 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/explore_best_sellers_icon_01_selected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Collect@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Collect@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01.imageset/Contents.json new file mode 100644 index 0000000..1534e3b --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Tags@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Tags@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01.imageset/Tags@2x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01.imageset/Tags@2x.png new file mode 100644 index 0000000..62f7ea8 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01.imageset/Tags@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01.imageset/Tags@3x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01.imageset/Tags@3x.png new file mode 100644 index 0000000..9d3baef Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01.imageset/Tags@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01_selected.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01_selected.imageset/Contents.json new file mode 100644 index 0000000..1534e3b --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01_selected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Tags@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Tags@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01_selected.imageset/Tags@2x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01_selected.imageset/Tags@2x.png new file mode 100644 index 0000000..5c6b019 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01_selected.imageset/Tags@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01_selected.imageset/Tags@3x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01_selected.imageset/Tags@3x.png new file mode 100644 index 0000000..03d8cb3 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_genres_icon_01_selected.imageset/Tags@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01.imageset/Collect@2x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01.imageset/Collect@2x.png new file mode 100644 index 0000000..46dc3fb Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01.imageset/Collect@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01.imageset/Collect@3x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01.imageset/Collect@3x.png new file mode 100644 index 0000000..cf35f43 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01.imageset/Collect@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01.imageset/Contents.json new file mode 100644 index 0000000..d9aeba1 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Collect@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Collect@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01_selected.imageset/Collect@2x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01_selected.imageset/Collect@2x.png new file mode 100644 index 0000000..d7be66b Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01_selected.imageset/Collect@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01_selected.imageset/Collect@3x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01_selected.imageset/Collect@3x.png new file mode 100644 index 0000000..0c965d5 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01_selected.imageset/Collect@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01_selected.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01_selected.imageset/Contents.json new file mode 100644 index 0000000..d9aeba1 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/explore_most_collected_icon_01_selected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Collect@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Collect@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01.imageset/Contents.json new file mode 100644 index 0000000..7822869 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Star-Rating@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Star-Rating@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01.imageset/Star-Rating@2x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01.imageset/Star-Rating@2x.png new file mode 100644 index 0000000..e9a11b6 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01.imageset/Star-Rating@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01.imageset/Star-Rating@3x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01.imageset/Star-Rating@3x.png new file mode 100644 index 0000000..3be3cb6 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01.imageset/Star-Rating@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01_selected.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01_selected.imageset/Contents.json new file mode 100644 index 0000000..7822869 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01_selected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Star-Rating@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Star-Rating@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01_selected.imageset/Star-Rating@2x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01_selected.imageset/Star-Rating@2x.png new file mode 100644 index 0000000..2eb1913 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01_selected.imageset/Star-Rating@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01_selected.imageset/Star-Rating@3x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01_selected.imageset/Star-Rating@3x.png new file mode 100644 index 0000000..28abf39 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_top_rated_icon_01_selected.imageset/Star-Rating@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01.imageset/Contents.json new file mode 100644 index 0000000..12ad516 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Heat@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Heat@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01.imageset/Heat@2x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01.imageset/Heat@2x.png new file mode 100644 index 0000000..0c7cafa Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01.imageset/Heat@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01.imageset/Heat@3x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01.imageset/Heat@3x.png new file mode 100644 index 0000000..336ab7a Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01.imageset/Heat@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01_selected.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01_selected.imageset/Contents.json new file mode 100644 index 0000000..12ad516 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01_selected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Heat@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Heat@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01_selected.imageset/Heat@2x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01_selected.imageset/Heat@2x.png new file mode 100644 index 0000000..3431462 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01_selected.imageset/Heat@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01_selected.imageset/Heat@3x.png b/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01_selected.imageset/Heat@3x.png new file mode 100644 index 0000000..4c2cca8 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/explore_trending_icon_01_selected.imageset/Heat@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/history_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/history_icon_01.imageset/Contents.json new file mode 100644 index 0000000..6187294 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/history_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "History@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "History@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/history_icon_01.imageset/History@2x.png b/ReaderHive/Source/Assets.xcassets/Image/history_icon_01.imageset/History@2x.png new file mode 100644 index 0000000..2bd5e8a Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/history_icon_01.imageset/History@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/history_icon_01.imageset/History@3x.png b/ReaderHive/Source/Assets.xcassets/Image/history_icon_01.imageset/History@3x.png new file mode 100644 index 0000000..ee4ffe2 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/history_icon_01.imageset/History@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/home_cell_bg_image_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/home_cell_bg_image_01.imageset/Contents.json new file mode 100644 index 0000000..cf508a2 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/home_cell_bg_image_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ListItem@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ListItem@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/home_cell_bg_image_01.imageset/ListItem@2x.png b/ReaderHive/Source/Assets.xcassets/Image/home_cell_bg_image_01.imageset/ListItem@2x.png new file mode 100644 index 0000000..4fc9e25 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/home_cell_bg_image_01.imageset/ListItem@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/home_cell_bg_image_01.imageset/ListItem@3x.png b/ReaderHive/Source/Assets.xcassets/Image/home_cell_bg_image_01.imageset/ListItem@3x.png new file mode 100644 index 0000000..18d83a5 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/home_cell_bg_image_01.imageset/ListItem@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/home_title_image.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/home_title_image.imageset/Contents.json new file mode 100644 index 0000000..6d7fdfc --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/home_title_image.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "EverRead@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "EverRead@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/home_title_image.imageset/EverRead@2x.png b/ReaderHive/Source/Assets.xcassets/Image/home_title_image.imageset/EverRead@2x.png new file mode 100644 index 0000000..4befc08 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/home_title_image.imageset/EverRead@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/home_title_image.imageset/EverRead@3x.png b/ReaderHive/Source/Assets.xcassets/Image/home_title_image.imageset/EverRead@3x.png new file mode 100644 index 0000000..eb05da0 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/home_title_image.imageset/EverRead@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/hot_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_01.imageset/Contents.json new file mode 100644 index 0000000..12ad516 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Heat@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Heat@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/hot_icon_01.imageset/Heat@2x.png b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_01.imageset/Heat@2x.png new file mode 100644 index 0000000..459513c Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_01.imageset/Heat@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/hot_icon_01.imageset/Heat@3x.png b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_01.imageset/Heat@3x.png new file mode 100644 index 0000000..d683366 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_01.imageset/Heat@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/hot_icon_02.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_02.imageset/Contents.json new file mode 100644 index 0000000..12ad516 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Heat@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Heat@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/hot_icon_02.imageset/Heat@2x.png b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_02.imageset/Heat@2x.png new file mode 100644 index 0000000..e721721 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_02.imageset/Heat@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/hot_icon_02.imageset/Heat@3x.png b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_02.imageset/Heat@3x.png new file mode 100644 index 0000000..ebf2b27 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_02.imageset/Heat@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/hot_icon_03.imageset/Badge@2x.png b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_03.imageset/Badge@2x.png new file mode 100644 index 0000000..b3f9319 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_03.imageset/Badge@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/hot_icon_03.imageset/Badge@3x.png b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_03.imageset/Badge@3x.png new file mode 100644 index 0000000..6264bee Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_03.imageset/Badge@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/hot_icon_03.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_03.imageset/Contents.json new file mode 100644 index 0000000..f3b1c89 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_03.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Badge@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Badge@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/hot_icon_04.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_04.imageset/Contents.json new file mode 100644 index 0000000..12ad516 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_04.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Heat@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Heat@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/hot_icon_04.imageset/Heat@2x.png b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_04.imageset/Heat@2x.png new file mode 100644 index 0000000..354e350 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_04.imageset/Heat@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/hot_icon_04.imageset/Heat@3x.png b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_04.imageset/Heat@3x.png new file mode 100644 index 0000000..63a8815 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/hot_icon_04.imageset/Heat@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/language_icon_01.imageset/Choose language@2x.png b/ReaderHive/Source/Assets.xcassets/Image/language_icon_01.imageset/Choose language@2x.png new file mode 100644 index 0000000..a5e7019 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/language_icon_01.imageset/Choose language@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/language_icon_01.imageset/Choose language@3x.png b/ReaderHive/Source/Assets.xcassets/Image/language_icon_01.imageset/Choose language@3x.png new file mode 100644 index 0000000..db8152a Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/language_icon_01.imageset/Choose language@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/language_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/language_icon_01.imageset/Contents.json new file mode 100644 index 0000000..8d93a07 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/language_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Choose language@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Choose language@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/lock_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/lock_icon_01.imageset/Contents.json new file mode 100644 index 0000000..65fe77e --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/lock_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Lock@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Lock@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/lock_icon_01.imageset/Lock@2x.png b/ReaderHive/Source/Assets.xcassets/Image/lock_icon_01.imageset/Lock@2x.png new file mode 100644 index 0000000..781f014 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/lock_icon_01.imageset/Lock@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/lock_icon_01.imageset/Lock@3x.png b/ReaderHive/Source/Assets.xcassets/Image/lock_icon_01.imageset/Lock@3x.png new file mode 100644 index 0000000..f043463 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/lock_icon_01.imageset/Lock@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/lock_icon_02.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/lock_icon_02.imageset/Contents.json new file mode 100644 index 0000000..65fe77e --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/lock_icon_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Lock@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Lock@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/lock_icon_02.imageset/Lock@2x.png b/ReaderHive/Source/Assets.xcassets/Image/lock_icon_02.imageset/Lock@2x.png new file mode 100644 index 0000000..9b3d903 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/lock_icon_02.imageset/Lock@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/lock_icon_02.imageset/Lock@3x.png b/ReaderHive/Source/Assets.xcassets/Image/lock_icon_02.imageset/Lock@3x.png new file mode 100644 index 0000000..6108791 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/lock_icon_02.imageset/Lock@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/logo_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/logo_icon_01.imageset/Contents.json new file mode 100644 index 0000000..d40e37d --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/logo_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Logo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Logo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/logo_icon_01.imageset/Logo@2x.png b/ReaderHive/Source/Assets.xcassets/Image/logo_icon_01.imageset/Logo@2x.png new file mode 100644 index 0000000..2d82743 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/logo_icon_01.imageset/Logo@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/logo_icon_01.imageset/Logo@3x.png b/ReaderHive/Source/Assets.xcassets/Image/logo_icon_01.imageset/Logo@3x.png new file mode 100644 index 0000000..b126148 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/logo_icon_01.imageset/Logo@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/more_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/more_icon_01.imageset/Contents.json new file mode 100644 index 0000000..1b54e50 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/more_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "More@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "More@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/more_icon_01.imageset/More@2x.png b/ReaderHive/Source/Assets.xcassets/Image/more_icon_01.imageset/More@2x.png new file mode 100644 index 0000000..1616057 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/more_icon_01.imageset/More@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/more_icon_01.imageset/More@3x.png b/ReaderHive/Source/Assets.xcassets/Image/more_icon_01.imageset/More@3x.png new file mode 100644 index 0000000..6f960b9 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/more_icon_01.imageset/More@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/new_icon_01.imageset/Badge@2x.png b/ReaderHive/Source/Assets.xcassets/Image/new_icon_01.imageset/Badge@2x.png new file mode 100644 index 0000000..de91ddd Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/new_icon_01.imageset/Badge@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/new_icon_01.imageset/Badge@3x.png b/ReaderHive/Source/Assets.xcassets/Image/new_icon_01.imageset/Badge@3x.png new file mode 100644 index 0000000..c0c0586 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/new_icon_01.imageset/Badge@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/new_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/new_icon_01.imageset/Contents.json new file mode 100644 index 0000000..f3b1c89 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/new_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Badge@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Badge@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/night_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/night_icon_01.imageset/Contents.json new file mode 100644 index 0000000..6df45bc --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/night_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "夜间模式@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "夜间模式@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/night_icon_01.imageset/夜间模式@2x.png b/ReaderHive/Source/Assets.xcassets/Image/night_icon_01.imageset/夜间模式@2x.png new file mode 100644 index 0000000..72db487 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/night_icon_01.imageset/夜间模式@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/night_icon_01.imageset/夜间模式@3x.png b/ReaderHive/Source/Assets.xcassets/Image/night_icon_01.imageset/夜间模式@3x.png new file mode 100644 index 0000000..a38c34e Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/night_icon_01.imageset/夜间模式@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/num_1_bg_icon.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/num_1_bg_icon.imageset/Contents.json new file mode 100644 index 0000000..9f9efad --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/num_1_bg_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Rectangle 1202@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Rectangle 1202@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/num_1_bg_icon.imageset/Rectangle 1202@2x.png b/ReaderHive/Source/Assets.xcassets/Image/num_1_bg_icon.imageset/Rectangle 1202@2x.png new file mode 100644 index 0000000..c0ba067 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/num_1_bg_icon.imageset/Rectangle 1202@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/num_1_bg_icon.imageset/Rectangle 1202@3x.png b/ReaderHive/Source/Assets.xcassets/Image/num_1_bg_icon.imageset/Rectangle 1202@3x.png new file mode 100644 index 0000000..46bd224 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/num_1_bg_icon.imageset/Rectangle 1202@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/num_2_bg_icon.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/num_2_bg_icon.imageset/Contents.json new file mode 100644 index 0000000..d9d1850 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/num_2_bg_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Rectangle 1203@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Rectangle 1203@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/num_2_bg_icon.imageset/Rectangle 1203@2x.png b/ReaderHive/Source/Assets.xcassets/Image/num_2_bg_icon.imageset/Rectangle 1203@2x.png new file mode 100644 index 0000000..c3f704f Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/num_2_bg_icon.imageset/Rectangle 1203@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/num_2_bg_icon.imageset/Rectangle 1203@3x.png b/ReaderHive/Source/Assets.xcassets/Image/num_2_bg_icon.imageset/Rectangle 1203@3x.png new file mode 100644 index 0000000..97770a4 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/num_2_bg_icon.imageset/Rectangle 1203@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/num_3_bg_icon.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/num_3_bg_icon.imageset/Contents.json new file mode 100644 index 0000000..a360358 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/num_3_bg_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Rectangle 1204@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Rectangle 1204@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/num_3_bg_icon.imageset/Rectangle 1204@2x.png b/ReaderHive/Source/Assets.xcassets/Image/num_3_bg_icon.imageset/Rectangle 1204@2x.png new file mode 100644 index 0000000..b89f9c5 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/num_3_bg_icon.imageset/Rectangle 1204@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/num_3_bg_icon.imageset/Rectangle 1204@3x.png b/ReaderHive/Source/Assets.xcassets/Image/num_3_bg_icon.imageset/Rectangle 1204@3x.png new file mode 100644 index 0000000..c36919c Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/num_3_bg_icon.imageset/Rectangle 1204@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/num_4_bg_icon.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/num_4_bg_icon.imageset/Contents.json new file mode 100644 index 0000000..65ee4a2 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/num_4_bg_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Rectangle 1205@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Rectangle 1205@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/num_4_bg_icon.imageset/Rectangle 1205@2x.png b/ReaderHive/Source/Assets.xcassets/Image/num_4_bg_icon.imageset/Rectangle 1205@2x.png new file mode 100644 index 0000000..e11b0ef Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/num_4_bg_icon.imageset/Rectangle 1205@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/num_4_bg_icon.imageset/Rectangle 1205@3x.png b/ReaderHive/Source/Assets.xcassets/Image/num_4_bg_icon.imageset/Rectangle 1205@3x.png new file mode 100644 index 0000000..71b8151 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/num_4_bg_icon.imageset/Rectangle 1205@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/placeholder_image.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/placeholder_image.imageset/Contents.json new file mode 100644 index 0000000..923f0c5 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/placeholder_image.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "图标@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "图标@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/placeholder_image.imageset/图标@2x.png b/ReaderHive/Source/Assets.xcassets/Image/placeholder_image.imageset/图标@2x.png new file mode 100644 index 0000000..0065469 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/placeholder_image.imageset/图标@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/placeholder_image.imageset/图标@3x.png b/ReaderHive/Source/Assets.xcassets/Image/placeholder_image.imageset/图标@3x.png new file mode 100644 index 0000000..9a2efaa Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/placeholder_image.imageset/图标@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/rate_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/rate_icon_01.imageset/Contents.json new file mode 100644 index 0000000..7822869 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/rate_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Star-Rating@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Star-Rating@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/rate_icon_01.imageset/Star-Rating@2x.png b/ReaderHive/Source/Assets.xcassets/Image/rate_icon_01.imageset/Star-Rating@2x.png new file mode 100644 index 0000000..dd57cb3 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/rate_icon_01.imageset/Star-Rating@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/rate_icon_01.imageset/Star-Rating@3x.png b/ReaderHive/Source/Assets.xcassets/Image/rate_icon_01.imageset/Star-Rating@3x.png new file mode 100644 index 0000000..f108b39 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/rate_icon_01.imageset/Star-Rating@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/search_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/search_icon_01.imageset/Contents.json new file mode 100644 index 0000000..702d2ad --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/search_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Search Button@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Search Button@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/search_icon_01.imageset/Search Button@2x.png b/ReaderHive/Source/Assets.xcassets/Image/search_icon_01.imageset/Search Button@2x.png new file mode 100644 index 0000000..f938be5 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/search_icon_01.imageset/Search Button@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/search_icon_01.imageset/Search Button@3x.png b/ReaderHive/Source/Assets.xcassets/Image/search_icon_01.imageset/Search Button@3x.png new file mode 100644 index 0000000..0fcf44a Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/search_icon_01.imageset/Search Button@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/search_icon_02.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/search_icon_02.imageset/Contents.json new file mode 100644 index 0000000..9de231d --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/search_icon_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Search@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Search@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/search_icon_02.imageset/Search@2x.png b/ReaderHive/Source/Assets.xcassets/Image/search_icon_02.imageset/Search@2x.png new file mode 100644 index 0000000..63b3837 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/search_icon_02.imageset/Search@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/search_icon_02.imageset/Search@3x.png b/ReaderHive/Source/Assets.xcassets/Image/search_icon_02.imageset/Search@3x.png new file mode 100644 index 0000000..f2122de Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/search_icon_02.imageset/Search@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/settings_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/settings_icon_01.imageset/Contents.json new file mode 100644 index 0000000..a3716c0 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/settings_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "设置@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "设置@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/settings_icon_01.imageset/设置@2x.png b/ReaderHive/Source/Assets.xcassets/Image/settings_icon_01.imageset/设置@2x.png new file mode 100644 index 0000000..ea67043 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/settings_icon_01.imageset/设置@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/settings_icon_01.imageset/设置@3x.png b/ReaderHive/Source/Assets.xcassets/Image/settings_icon_01.imageset/设置@3x.png new file mode 100644 index 0000000..50c0c31 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/settings_icon_01.imageset/设置@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/star_icon_01.imageset/Contents.json new file mode 100644 index 0000000..7822869 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/star_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Star-Rating@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Star-Rating@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_01.imageset/Star-Rating@2x.png b/ReaderHive/Source/Assets.xcassets/Image/star_icon_01.imageset/Star-Rating@2x.png new file mode 100644 index 0000000..05ce52e Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/star_icon_01.imageset/Star-Rating@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_01.imageset/Star-Rating@3x.png b/ReaderHive/Source/Assets.xcassets/Image/star_icon_01.imageset/Star-Rating@3x.png new file mode 100644 index 0000000..4dc4bb0 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/star_icon_01.imageset/Star-Rating@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_02.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/star_icon_02.imageset/Contents.json new file mode 100644 index 0000000..7822869 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/star_icon_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Star-Rating@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Star-Rating@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_02.imageset/Star-Rating@2x.png b/ReaderHive/Source/Assets.xcassets/Image/star_icon_02.imageset/Star-Rating@2x.png new file mode 100644 index 0000000..5283e33 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/star_icon_02.imageset/Star-Rating@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_02.imageset/Star-Rating@3x.png b/ReaderHive/Source/Assets.xcassets/Image/star_icon_02.imageset/Star-Rating@3x.png new file mode 100644 index 0000000..818217d Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/star_icon_02.imageset/Star-Rating@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_03.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/star_icon_03.imageset/Contents.json new file mode 100644 index 0000000..7822869 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/star_icon_03.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Star-Rating@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Star-Rating@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_03.imageset/Star-Rating@2x.png b/ReaderHive/Source/Assets.xcassets/Image/star_icon_03.imageset/Star-Rating@2x.png new file mode 100644 index 0000000..aec6588 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/star_icon_03.imageset/Star-Rating@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_03.imageset/Star-Rating@3x.png b/ReaderHive/Source/Assets.xcassets/Image/star_icon_03.imageset/Star-Rating@3x.png new file mode 100644 index 0000000..816c710 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/star_icon_03.imageset/Star-Rating@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_04.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/star_icon_04.imageset/Contents.json new file mode 100644 index 0000000..7822869 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/star_icon_04.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Star-Rating@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Star-Rating@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_04.imageset/Star-Rating@2x.png b/ReaderHive/Source/Assets.xcassets/Image/star_icon_04.imageset/Star-Rating@2x.png new file mode 100644 index 0000000..f21387e Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/star_icon_04.imageset/Star-Rating@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_04.imageset/Star-Rating@3x.png b/ReaderHive/Source/Assets.xcassets/Image/star_icon_04.imageset/Star-Rating@3x.png new file mode 100644 index 0000000..d8a2ccd Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/star_icon_04.imageset/Star-Rating@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_05.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/star_icon_05.imageset/Contents.json new file mode 100644 index 0000000..7822869 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/star_icon_05.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Star-Rating@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Star-Rating@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_05.imageset/Star-Rating@2x.png b/ReaderHive/Source/Assets.xcassets/Image/star_icon_05.imageset/Star-Rating@2x.png new file mode 100644 index 0000000..f03a99e Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/star_icon_05.imageset/Star-Rating@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/star_icon_05.imageset/Star-Rating@3x.png b/ReaderHive/Source/Assets.xcassets/Image/star_icon_05.imageset/Star-Rating@3x.png new file mode 100644 index 0000000..1c697fb Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/star_icon_05.imageset/Star-Rating@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/word_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/Image/word_icon_01.imageset/Contents.json new file mode 100644 index 0000000..2e5b2b6 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/Image/word_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Ticket@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Ticket@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/Image/word_icon_01.imageset/Ticket@2x.png b/ReaderHive/Source/Assets.xcassets/Image/word_icon_01.imageset/Ticket@2x.png new file mode 100644 index 0000000..b29ead2 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/word_icon_01.imageset/Ticket@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/Image/word_icon_01.imageset/Ticket@3x.png b/ReaderHive/Source/Assets.xcassets/Image/word_icon_01.imageset/Ticket@3x.png new file mode 100644 index 0000000..cb8c0a1 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/Image/word_icon_01.imageset/Ticket@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/LaunchScreen/Contents.json b/ReaderHive/Source/Assets.xcassets/LaunchScreen/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/LaunchScreen/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/Contents.json new file mode 100644 index 0000000..d99a60c --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "启动页@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "启动页@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/启动页@2x.png b/ReaderHive/Source/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/启动页@2x.png new file mode 100644 index 0000000..230c423 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/启动页@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/启动页@3x.png b/ReaderHive/Source/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/启动页@3x.png new file mode 100644 index 0000000..abdb349 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/启动页@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/Contents.json b/ReaderHive/Source/Assets.xcassets/TabBar/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/TabBar/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01.imageset/Contents.json new file mode 100644 index 0000000..deb4332 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Tab Icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Tab Icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01.imageset/Tab Icon@2x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01.imageset/Tab Icon@2x.png new file mode 100644 index 0000000..2442a52 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01.imageset/Tab Icon@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01.imageset/Tab Icon@3x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01.imageset/Tab Icon@3x.png new file mode 100644 index 0000000..c937e7c Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01.imageset/Tab Icon@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01_selected.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01_selected.imageset/Contents.json new file mode 100644 index 0000000..deb4332 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01_selected.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Tab Icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Tab Icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01_selected.imageset/Tab Icon@2x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01_selected.imageset/Tab Icon@2x.png new file mode 100644 index 0000000..55ce670 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01_selected.imageset/Tab Icon@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01_selected.imageset/Tab Icon@3x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01_selected.imageset/Tab Icon@3x.png new file mode 100644 index 0000000..e3711f8 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_01_selected.imageset/Tab Icon@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02.imageset/Contents.json new file mode 100644 index 0000000..deb4332 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Tab Icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Tab Icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02.imageset/Tab Icon@2x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02.imageset/Tab Icon@2x.png new file mode 100644 index 0000000..75064ef Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02.imageset/Tab Icon@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02.imageset/Tab Icon@3x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02.imageset/Tab Icon@3x.png new file mode 100644 index 0000000..2116117 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02.imageset/Tab Icon@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02_selected.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02_selected.imageset/Contents.json new file mode 100644 index 0000000..deb4332 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02_selected.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Tab Icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Tab Icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02_selected.imageset/Tab Icon@2x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02_selected.imageset/Tab Icon@2x.png new file mode 100644 index 0000000..2aa9e22 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02_selected.imageset/Tab Icon@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02_selected.imageset/Tab Icon@3x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02_selected.imageset/Tab Icon@3x.png new file mode 100644 index 0000000..448885a Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_02_selected.imageset/Tab Icon@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03.imageset/Contents.json new file mode 100644 index 0000000..deb4332 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Tab Icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Tab Icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03.imageset/Tab Icon@2x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03.imageset/Tab Icon@2x.png new file mode 100644 index 0000000..3ed8c81 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03.imageset/Tab Icon@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03.imageset/Tab Icon@3x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03.imageset/Tab Icon@3x.png new file mode 100644 index 0000000..e8eaf36 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03.imageset/Tab Icon@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03_selected.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03_selected.imageset/Contents.json new file mode 100644 index 0000000..deb4332 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03_selected.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Tab Icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Tab Icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03_selected.imageset/Tab Icon@2x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03_selected.imageset/Tab Icon@2x.png new file mode 100644 index 0000000..320291c Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03_selected.imageset/Tab Icon@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03_selected.imageset/Tab Icon@3x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03_selected.imageset/Tab Icon@3x.png new file mode 100644 index 0000000..e15bca4 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_03_selected.imageset/Tab Icon@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04.imageset/Contents.json new file mode 100644 index 0000000..deb4332 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Tab Icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Tab Icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04.imageset/Tab Icon@2x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04.imageset/Tab Icon@2x.png new file mode 100644 index 0000000..7a25a95 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04.imageset/Tab Icon@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04.imageset/Tab Icon@3x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04.imageset/Tab Icon@3x.png new file mode 100644 index 0000000..614b625 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04.imageset/Tab Icon@3x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04_selected.imageset/Contents.json b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04_selected.imageset/Contents.json new file mode 100644 index 0000000..deb4332 --- /dev/null +++ b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04_selected.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Tab Icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Tab Icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04_selected.imageset/Tab Icon@2x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04_selected.imageset/Tab Icon@2x.png new file mode 100644 index 0000000..db317dc Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04_selected.imageset/Tab Icon@2x.png differ diff --git a/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04_selected.imageset/Tab Icon@3x.png b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04_selected.imageset/Tab Icon@3x.png new file mode 100644 index 0000000..79f3bb2 Binary files /dev/null and b/ReaderHive/Source/Assets.xcassets/TabBar/tab_bar_icon_04_selected.imageset/Tab Icon@3x.png differ diff --git a/ReaderHive/Source/Base.lproj/LaunchScreen.storyboard b/ReaderHive/Source/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..8fedf0f --- /dev/null +++ b/ReaderHive/Source/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReaderHive/Source/Info.plist b/ReaderHive/Source/Info.plist new file mode 100644 index 0000000..2b56941 --- /dev/null +++ b/ReaderHive/Source/Info.plist @@ -0,0 +1,25 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UIDesignRequiresCompatibility + + + diff --git a/ReaderHive/Source/en.lproj/Localizable.strings b/ReaderHive/Source/en.lproj/Localizable.strings new file mode 100644 index 0000000..fb4976e --- /dev/null +++ b/ReaderHive/Source/en.lproj/Localizable.strings @@ -0,0 +1,90 @@ +/* + Localizable.strings + ReaderHive + + Created by 湖北秦九 on 2025/11/21. + +*/ + + +"Error" = "Error"; +"My List" = "My List"; +"Home" = "Home"; +"Explore" = "Explore"; +"Me" = "Me"; +"Must-Read Today" = "Must-Read Today"; +"New Arrivals" = "New Arrivals"; +"read_what_title" = "Not Sure what to read?"; +"read_what_subtitle" = "Our Editors have some suggests."; +"Read Now" = "Read Now"; +"Hot On The Grid" = "Hot On The Grid"; +"Next In View" = "Next In View"; +"Hot Tags" = "Hot Tags"; +"Featured" = "Featured"; +"More Stories" = "More Stories"; +"search_placeholder" = "What are you looking for?"; +"Recent Searches" = "Recent Searches"; +"Genres" = "Genres"; +"Trending" = "Trending"; +"Top Rated" = "Top Rated"; +"Most Collected" = "Most Collected"; +"Best Sellers" = "Best Sellers"; +"Today" = "Today"; +"This Week" = "This Week"; +"This Month" = "This Month"; +"Start Reading" = "Start Reading"; +"Collect" = "Collect"; +"Collects" = "Collects"; +"Heats" = "Heats"; +"Words" = "Words"; +"Rate" = "Rate"; +"Synopsis" = "Synopsis"; +"Completed" = "Completed"; +"More Like This" = "More Like This"; +"## Chapters" = "## Chapters"; +"Catalog" = "Catalog"; +"Night" = "Night"; +"Settings" = "Settings"; +"Prev" = "Prev"; +"Next" = "Next"; +"Brightness" = "Brightness"; +"Theme" = "Theme"; +"Line Spacing" = "Line Spacing"; +"Paragraph Spacing" = "Paragraph Spacing"; +"Small" = "Small"; +"Standard" = "Standard"; +"Large" = "Large"; +"Chapter.##" = "Chapter.##"; +"## read" = "## read"; +"Font Size" = "Font Size"; +"Add to My List" = "Add to My List"; +"In My List" = "In My List"; +"Enjoying this book?" = "Enjoying this book?"; +"My Rate:##" = "My Rate:##"; +"Rating Submitted!" = "Rating Submitted!"; +"read_finish_title" = "You Finished!"; +"read_finish_text" = "Congratulations On Completing This Epic Story!"; +"read_finish_list_title" = "You May Also Like"; +"Success" = "Success"; +"History" = "History"; +"About" = "About"; +"Privacy Policy" = "Privacy Policy"; +"User Agreement" = "User Agreement"; +"Visit Website" = "Visit Website"; +"Ch.##" = "Ch.##"; +"New" = "New"; +"alert_title_01" = "Recommend this book?"; +"alert_detail_01" = "Help us share this story with others."; +"Yes, Recommend" = "Yes, Recommend"; +"Not unlocked yet" = "Not unlocked yet"; +"No data" = "No data"; +"Coins" = "Coins"; +"Bonus" = "Bonus"; +"Top Up" = "Top Up"; +"Language" = "Language"; + + + + +"network_error_1" = "Your account is already logged in on another device~"; +"network_error_2" = "The service is abnormal. Check the network.";