From c74c1e8b07abfc0a47ba28d262e6fec9730e17d7 Mon Sep 17 00:00:00 2001 From: ned Date: Wed, 2 Oct 2019 17:17:21 +0200 Subject: [PATCH] added wishes/automatic requests --- appdb.xcodeproj/project.pbxproj | 70 +++++- appdb/API/API+Wishes.swift | 42 ++++ appdb/API/API.swift | 2 + appdb/Models/WishApp.swift | 67 ++++++ .../wishes.imageset/Contents.json | 26 +++ .../wishes.imageset/Untitled.png | Bin 0 -> 737 bytes .../wishes.imageset/Untitled@2x.png | Bin 0 -> 1287 bytes .../wishes.imageset/Untitled@3x.png | Bin 0 -> 1783 bytes .../AdaptiveUIAlertController.swift" | 2 +- ...AskBundleBeforeUploadViewController.swift" | 2 +- .../LoadingTableView.swift" | 9 +- appdb/Startup/Extensions.swift | 7 + appdb/Tabs/Featured/Featured/Featured.swift | 24 +++ .../Featured/Wishes/Cells/WishAppCell.swift | 110 ++++++++++ .../Wishes/Fulfilled/FulfilledWishes.swift | 118 +++++++++++ .../Tabs/Featured/Wishes/New/NewWishes.swift | 135 ++++++++++++ appdb/Tabs/Featured/Wishes/Wishes.swift | 200 ++++++++++++++++++ .../Settings/Theme Chooser/ThemeChooser.swift | 14 +- appdb/en.lproj/Localizable.strings | 20 ++ appdb/es.lproj/Localizable.strings | 21 ++ appdb/it.lproj/Localizable.strings | 20 ++ appdb/ru.lproj/Localizable.strings | 20 ++ 22 files changed, 894 insertions(+), 15 deletions(-) create mode 100644 appdb/API/API+Wishes.swift create mode 100644 appdb/Models/WishApp.swift create mode 100644 appdb/Other/Images.xcassets/wishes.imageset/Contents.json create mode 100644 appdb/Other/Images.xcassets/wishes.imageset/Untitled.png create mode 100644 appdb/Other/Images.xcassets/wishes.imageset/Untitled@2x.png create mode 100644 appdb/Other/Images.xcassets/wishes.imageset/Untitled@3x.png create mode 100644 appdb/Tabs/Featured/Wishes/Cells/WishAppCell.swift create mode 100644 appdb/Tabs/Featured/Wishes/Fulfilled/FulfilledWishes.swift create mode 100644 appdb/Tabs/Featured/Wishes/New/NewWishes.swift create mode 100644 appdb/Tabs/Featured/Wishes/Wishes.swift diff --git a/appdb.xcodeproj/project.pbxproj b/appdb.xcodeproj/project.pbxproj index 07444a8a..d8e93b86 100644 --- a/appdb.xcodeproj/project.pbxproj +++ b/appdb.xcodeproj/project.pbxproj @@ -73,6 +73,12 @@ 814F4FBB2196FA35005A8111 /* UpdateableApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 814F4FBA2196FA35005A8111 /* UpdateableApp.swift */; }; 814F4FBD21974A5B005A8111 /* Ignored.swift in Sources */ = {isa = PBXBuildFile; fileRef = 814F4FBC21974A5B005A8111 /* Ignored.swift */; }; 814F4FBF2197589E005A8111 /* IgnoredCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 814F4FBE2197589E005A8111 /* IgnoredCell.swift */; }; + 815016932312DA8200E06E1E /* API+Wishes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 815016922312DA8200E06E1E /* API+Wishes.swift */; }; + 815016952312DAAE00E06E1E /* WishApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 815016942312DAAE00E06E1E /* WishApp.swift */; }; + 815016A12312DAFE00E06E1E /* WishAppCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8150169B2312DAFE00E06E1E /* WishAppCell.swift */; }; + 815016A22312DAFE00E06E1E /* Wishes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8150169C2312DAFE00E06E1E /* Wishes.swift */; }; + 815016A32312DAFE00E06E1E /* NewWishes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8150169E2312DAFE00E06E1E /* NewWishes.swift */; }; + 815016A42312DAFE00E06E1E /* FulfilledWishes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 815016A02312DAFE00E06E1E /* FulfilledWishes.swift */; }; 815234CF2278479000B1AE65 /* ZIPFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 815234CE2278479000B1AE65 /* ZIPFoundation.framework */; }; 815234D22278A6BF00B1AE65 /* SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 815234D12278A6BF00B1AE65 /* SHA1.swift */; }; 815234D52278C10A00B1AE65 /* BackgroundTaskUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 815234D42278C10A00B1AE65 /* BackgroundTaskUtil.swift */; }; @@ -155,6 +161,7 @@ 81A9DBB8214D36BD00AB1A8C /* Acknowledgements.plist in Resources */ = {isa = PBXBuildFile; fileRef = 819EC27220A72472007138CF /* Acknowledgements.plist */; }; 81AB37AE1DAC30BD003A586F /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81AB37AD1DAC30BD003A586F /* TabBarController.swift */; }; 81AF3532215115B200A798D7 /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81AF3531215115B200A798D7 /* RoundedButton.swift */; }; + 81B0A9802341387C00E14A71 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81B0A97F2341387C00E14A71 /* StoreKit.framework */; }; 81BE066C1E6C2EA500AD9827 /* Details+ExternalLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81BE066B1E6C2EA500AD9827 /* Details+ExternalLink.swift */; }; 81BE066F1E6C52FE00AD9827 /* Details+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81BE066E1E6C52FE00AD9827 /* Details+Publisher.swift */; }; 81BE06721E6CB6DE00AD9827 /* Details+SegmentControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81BE06711E6CB6DE00AD9827 /* Details+SegmentControl.swift */; }; @@ -163,7 +170,6 @@ 81BFC98B22F4CE41008073D8 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81BFC98A22F4CE41008073D8 /* CoreTelephony.framework */; }; 81BFC98D22F4CE49008073D8 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81BFC98C22F4CE49008073D8 /* SystemConfiguration.framework */; }; 81BFC98F22F4CE50008073D8 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81BFC98E22F4CE50008073D8 /* CoreGraphics.framework */; }; - 81BFC99122F4CE57008073D8 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81BFC99022F4CE57008073D8 /* StoreKit.framework */; }; 81BFC99322F4CE64008073D8 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81BFC99222F4CE64008073D8 /* AdSupport.framework */; }; 81BFC99522F4CE6C008073D8 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81BFC99422F4CE6C008073D8 /* QuartzCore.framework */; }; 81BFC99722F4CE73008073D8 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81BFC99622F4CE72008073D8 /* CoreMedia.framework */; }; @@ -303,6 +309,12 @@ 814F4FBA2196FA35005A8111 /* UpdateableApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateableApp.swift; sourceTree = ""; }; 814F4FBC21974A5B005A8111 /* Ignored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ignored.swift; sourceTree = ""; }; 814F4FBE2197589E005A8111 /* IgnoredCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IgnoredCell.swift; sourceTree = ""; }; + 815016922312DA8200E06E1E /* API+Wishes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "API+Wishes.swift"; sourceTree = ""; }; + 815016942312DAAE00E06E1E /* WishApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WishApp.swift; sourceTree = ""; }; + 8150169B2312DAFE00E06E1E /* WishAppCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WishAppCell.swift; sourceTree = ""; }; + 8150169C2312DAFE00E06E1E /* Wishes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wishes.swift; sourceTree = ""; }; + 8150169E2312DAFE00E06E1E /* NewWishes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewWishes.swift; sourceTree = ""; }; + 815016A02312DAFE00E06E1E /* FulfilledWishes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FulfilledWishes.swift; sourceTree = ""; }; 815234CE2278479000B1AE65 /* ZIPFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ZIPFoundation.framework; path = Carthage/Build/iOS/ZIPFoundation.framework; sourceTree = ""; }; 815234D12278A6BF00B1AE65 /* SHA1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SHA1.swift; sourceTree = ""; }; 815234D42278C10A00B1AE65 /* BackgroundTaskUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskUtil.swift; sourceTree = ""; }; @@ -389,6 +401,7 @@ 819EC2D31E41016D00BA7A50 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; usesTabs = 1; }; 81AB37AD1DAC30BD003A586F /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TabBarController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 81AF3531215115B200A798D7 /* RoundedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedButton.swift; sourceTree = ""; }; + 81B0A97F2341387C00E14A71 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; 81BE066B1E6C2EA500AD9827 /* Details+ExternalLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "Details+ExternalLink.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 81BE066E1E6C52FE00AD9827 /* Details+Publisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Details+Publisher.swift"; sourceTree = ""; }; 81BE06711E6CB6DE00AD9827 /* Details+SegmentControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Details+SegmentControl.swift"; sourceTree = ""; }; @@ -397,7 +410,6 @@ 81BFC98A22F4CE41008073D8 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; 81BFC98C22F4CE49008073D8 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 81BFC98E22F4CE50008073D8 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; - 81BFC99022F4CE57008073D8 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; 81BFC99222F4CE64008073D8 /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; }; 81BFC99422F4CE6C008073D8 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 81BFC99622F4CE72008073D8 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; @@ -465,16 +477,16 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 81BFC99922F4CE7B008073D8 /* AVFoundation.framework in Frameworks */, + 81BFC98B22F4CE41008073D8 /* CoreTelephony.framework in Frameworks */, + 81B0A9802341387C00E14A71 /* StoreKit.framework in Frameworks */, 81BFC99D22F4CE8B008073D8 /* libz.tbd in Frameworks */, 81BFC99B22F4CE81008073D8 /* WebKit.framework in Frameworks */, - 81BFC99922F4CE7B008073D8 /* AVFoundation.framework in Frameworks */, 81BFC99722F4CE73008073D8 /* CoreMedia.framework in Frameworks */, 81BFC99522F4CE6C008073D8 /* QuartzCore.framework in Frameworks */, 81BFC99322F4CE64008073D8 /* AdSupport.framework in Frameworks */, - 81BFC99122F4CE57008073D8 /* StoreKit.framework in Frameworks */, 81BFC98F22F4CE50008073D8 /* CoreGraphics.framework in Frameworks */, 81BFC98D22F4CE49008073D8 /* SystemConfiguration.framework in Frameworks */, - 81BFC98B22F4CE41008073D8 /* CoreTelephony.framework in Frameworks */, 815234CF2278479000B1AE65 /* ZIPFoundation.framework in Frameworks */, 817D2EDA1E8EBBE500B8AE0D /* Alamofire.framework in Frameworks */, 817D2EDD1E8EBBE500B8AE0D /* Cartography.framework in Frameworks */, @@ -583,8 +595,8 @@ 812E97861E5772BD00C1379F /* Search */ = { isa = PBXGroup; children = ( - 81485A76216617980004184F /* Search+Extension.swift */, 815C4A9F1F954FDA0002AB08 /* Search.swift */, + 81485A76216617980004184F /* Search+Extension.swift */, 815C4AA11F9550340002AB08 /* SuggestionsWhileTyping.swift */, 81A060241F8A24E9004EEEE1 /* Search Cells */, 81603A69216E638E00F18E34 /* Trending View */, @@ -667,6 +679,41 @@ path = "Device Link"; sourceTree = ""; }; + 815016992312DAFE00E06E1E /* Wishes */ = { + isa = PBXGroup; + children = ( + 8150169C2312DAFE00E06E1E /* Wishes.swift */, + 8150169A2312DAFE00E06E1E /* Cells */, + 8150169D2312DAFE00E06E1E /* New */, + 8150169F2312DAFE00E06E1E /* Fulfilled */, + ); + path = Wishes; + sourceTree = ""; + }; + 8150169A2312DAFE00E06E1E /* Cells */ = { + isa = PBXGroup; + children = ( + 8150169B2312DAFE00E06E1E /* WishAppCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; + 8150169D2312DAFE00E06E1E /* New */ = { + isa = PBXGroup; + children = ( + 8150169E2312DAFE00E06E1E /* NewWishes.swift */, + ); + path = New; + sourceTree = ""; + }; + 8150169F2312DAFE00E06E1E /* Fulfilled */ = { + isa = PBXGroup; + children = ( + 815016A02312DAFE00E06E1E /* FulfilledWishes.swift */, + ); + path = Fulfilled; + sourceTree = ""; + }; 815234D02278A6AD00B1AE65 /* SHA1 */ = { isa = PBXGroup; children = ( @@ -1068,6 +1115,7 @@ 81AB37A31DAC2ED7003A586F /* Frameworks */ = { isa = PBXGroup; children = ( + 81B0A97F2341387C00E14A71 /* StoreKit.framework */, 81BFC98722F4CE0A008073D8 /* StartApp.framework */, 81BFC99C22F4CE8A008073D8 /* libz.tbd */, 81BFC99A22F4CE81008073D8 /* WebKit.framework */, @@ -1075,7 +1123,6 @@ 81BFC99622F4CE72008073D8 /* CoreMedia.framework */, 81BFC99422F4CE6C008073D8 /* QuartzCore.framework */, 81BFC99222F4CE64008073D8 /* AdSupport.framework */, - 81BFC99022F4CE57008073D8 /* StoreKit.framework */, 81BFC98E22F4CE50008073D8 /* CoreGraphics.framework */, 81BFC98C22F4CE49008073D8 /* SystemConfiguration.framework */, 81BFC98A22F4CE41008073D8 /* CoreTelephony.framework */, @@ -1138,6 +1185,7 @@ 817610ED21515A3200602FF2 /* API+Links.swift */, 81FC8B4022735E5E003788F3 /* API+MyAppstore.swift */, 817610EE21515A3200602FF2 /* API+News.swift */, + 815016922312DA8200E06E1E /* API+Wishes.swift */, 817610E821515A3100602FF2 /* API+Promotions.swift */, 817610E921515A3200602FF2 /* API+Search.swift */, 817610E521515A3100602FF2 /* API+SystemStatus.swift */, @@ -1226,6 +1274,7 @@ 811BEE1E2151538500F1F56A /* Items.swift */, 811BEE1F2151538600F1F56A /* Link.swift */, 81FC8B4222735EA8003788F3 /* MyAppstoreApp.swift */, + 815016942312DAAE00E06E1E /* WishApp.swift */, 811BEE222151538600F1F56A /* Promotion.swift */, 81FCAFCF226E1D0C004FAFD6 /* RequestedApp.swift */, 811BEE252151538600F1F56A /* ServiceStatus.swift */, @@ -1255,6 +1304,7 @@ 816CEED71DBCE88E002CFF6E /* Categories */, 817FF9ED1E59CC900056960F /* Details */, 8133C45A208B627E00F2F9F5 /* See All */, + 815016992312DAFE00E06E1E /* Wishes */, 81DFE219229DCA3E009BD7C1 /* Update Prompt */, ); path = Featured; @@ -1565,6 +1615,7 @@ 817418A52165368A00BB70EB /* LoadingCollectionView.swift in Sources */, 818D75F222CCF32900A69C56 /* AdHelper.swift in Sources */, 81FC8B4122735E5E003788F3 /* API+MyAppstore.swift in Sources */, + 815016A12312DAFE00E06E1E /* WishAppCell.swift in Sources */, 814F4FB92196F5F9005A8111 /* API+Updates.swift in Sources */, 8105175B20D0283300B3E1FB /* Identifiable.swift in Sources */, 81F271E221690B97005C7314 /* NoScreenshotsSearchCell~Book+Stars.swift in Sources */, @@ -1572,12 +1623,14 @@ 8133C45E208B874600F2F9F5 /* SeeAllCell.swift in Sources */, 81EC00751DAD89AF009EEFFA /* Dummy.swift in Sources */, 81F1FEA41E5B7DEF00C1CD44 /* AlamofireObjectMapper.swift in Sources */, + 815016A42312DAFE00E06E1E /* FulfilledWishes.swift in Sources */, 81F055ED2298147200E76874 /* KeychainItemAccessibility.swift in Sources */, 8148D85C226DE68E006AD9E0 /* Downloading.swift in Sources */, 81DFE21B229DCA66009BD7C1 /* AppUpdateHeader.swift in Sources */, 818A55AC20584F2B00162E51 /* BannerImage.swift in Sources */, 81236AC91E3B6899009748E0 /* ButtonFactory.swift in Sources */, 818A55A120583D4F00162E51 /* Updates.swift in Sources */, + 815016932312DA8200E06E1E /* API+Wishes.swift in Sources */, 8126201D215444A400B06F88 /* NSAttributedString+Utils.swift in Sources */, 8189794922859EED00EC87B2 /* IPAWebViewController.swift in Sources */, 817A53D81E5F24540081ED26 /* Details+Description.swift in Sources */, @@ -1626,7 +1679,9 @@ 81485A77216617980004184F /* Search+Extension.swift in Sources */, 8126201C215444A400B06F88 /* Style.swift in Sources */, 81DEB2A91E7DD03100A94B26 /* Details+DownloadCell.swift in Sources */, + 815016952312DAAE00E06E1E /* WishApp.swift in Sources */, 811BEE2D2151538600F1F56A /* App.swift in Sources */, + 815016A22312DAFE00E06E1E /* Wishes.swift in Sources */, 81D661761E5A0B6C00BB6461 /* Details+Extension.swift in Sources */, 815234D22278A6BF00B1AE65 /* SHA1.swift in Sources */, 814B7710205B2DCA004D46DD /* RefreshView.swift in Sources */, @@ -1682,6 +1737,7 @@ 81431AA120A0CC1E00C830BE /* UIDeviceExtension.swift in Sources */, 8189794722848D8100EC87B2 /* ObserveDownloadingApps.swift in Sources */, 81AF3532215115B200A798D7 /* RoundedButton.swift in Sources */, + 815016A32312DAFE00E06E1E /* NewWishes.swift in Sources */, 81EC00681DAD76EC009EEFFA /* Extensions.swift in Sources */, 819E69891F8E8C2700C4608F /* SearchCell.swift in Sources */, 818979412284853A00EC87B2 /* DownloadingCell.swift in Sources */, diff --git a/appdb/API/API+Wishes.swift b/appdb/API/API+Wishes.swift new file mode 100644 index 00000000..315e3d7a --- /dev/null +++ b/appdb/API/API+Wishes.swift @@ -0,0 +1,42 @@ +// +// API+Wishes.swift +// appdb +// +// Created by ned on 07/07/2019. +// Copyright © 2019 ned. All rights reserved. +// + +import Alamofire +import SwiftyJSON + +extension API { + + static func createPublishRequest(appStoreUrl: String, type: String = "ios", completion:@escaping (_ error: String?) -> Void) { + Alamofire.request(endpoint, parameters: ["action": Actions.createPublishRequest.rawValue, "url": appStoreUrl, "type": type], headers: headersWithCookie) + .responseJSON { response in + switch response.result { + case .success(let value): + let json = JSON(value) + if !json["success"].boolValue { + completion(json["errors"][0].stringValue) + } else { + completion(nil) + } + case .failure(let error): + completion(error.localizedDescription) + } + } + } + + static func getPublishRequests(includeAll: Bool, page: Int = 1, success:@escaping (_ items: [WishApp]) -> Void, fail:@escaping (_ error: String) -> Void) { + Alamofire.request(endpoint, parameters: ["action": Actions.getPublishRequests.rawValue, "type": "ios", "include_all": includeAll ? 1 : 0, "page": page], headers: headers) + .responseArray(keyPath: "data") { (response: DataResponse<[WishApp]>) in + switch response.result { + case .success(let results): + success(results) + case .failure(let error): + fail(error.localizedDescription) + } + } + } +} diff --git a/appdb/API/API.swift b/appdb/API/API.swift index d6f495ca..5ab56cf2 100644 --- a/appdb/API/API.swift +++ b/appdb/API/API.swift @@ -81,6 +81,8 @@ enum Actions: String { case deleteIpa = "delete_ipa" case addIpa = "add_ipa" case analyzeIpa = "get_ipa_analyze_jobs" + case createPublishRequest = "create_publish_request" + case getPublishRequests = "get_publish_requests" } enum ConfigurationParameters: String { diff --git a/appdb/Models/WishApp.swift b/appdb/Models/WishApp.swift new file mode 100644 index 00000000..8a650315 --- /dev/null +++ b/appdb/Models/WishApp.swift @@ -0,0 +1,67 @@ +// +// WishApp.swift +// appdb +// +// Created by ned on 07/07/2019. +// Copyright © 2019 ned. All rights reserved. +// + +import ObjectMapper +import Localize_Swift + +struct WishApp: Mappable { + + init?(map: Map) { } + + var id: String = "" + var trackid: String = "" + var version: String = "" + var image: String = "" + var name: String = "" + var requestersAmount: String = "" + var price: String = "" + var statusString: String = "" + var status: Status = .new + var statusChangedAt: String = "" + var bundleId: String = "" + + enum Status: String { + case cracking, fulfilled, failed, new + + // todo localize + var prettified: String { + switch self { + case .new: return "New".localized() + case .cracking: return "⚙︎ " + "Processing".localized() + case .failed: return "𐄂 " + "Failed".localized() + case .fulfilled: return "✓ " + "Fulfilled".localized() + } + } + } + + mutating func mapping(map: Map) { + id <- map["id"] + trackid <- map["trackid"] + version <- map["version"] + image <- map["image"] + name <- map["name"] + requestersAmount <- map["requesters_amount"] + price <- map["price"] + statusString <- map["status"] + statusChangedAt <- map["status_changed_at"] + bundleId <- map["bundle_id"] + + name = name.decoded + + price = price == "0.00" ? "Free".localized() : price + + status = Status(rawValue: statusString) ?? .new + + let date = statusChangedAt.unixToDate + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: Localize.currentLanguage()) + dateFormatter.dateStyle = .medium + dateFormatter.timeStyle = .short + statusChangedAt = dateFormatter.string(from: date) + } +} diff --git a/appdb/Other/Images.xcassets/wishes.imageset/Contents.json b/appdb/Other/Images.xcassets/wishes.imageset/Contents.json new file mode 100644 index 00000000..104f5bc0 --- /dev/null +++ b/appdb/Other/Images.xcassets/wishes.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Untitled.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Untitled@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "Untitled@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/appdb/Other/Images.xcassets/wishes.imageset/Untitled.png b/appdb/Other/Images.xcassets/wishes.imageset/Untitled.png new file mode 100644 index 0000000000000000000000000000000000000000..14a7f25c4845ee0467615ee1aca1ba447238ed09 GIT binary patch literal 737 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a5n0T@z%2~Ij105p zNH8!kMrMXYltlRYSS9D@>LsS+C#C9DVstT4fPE4;bsH1+JHo@{EISEfi{E8 zw==W>t3(ll+GC>+vK+}V5TAlYfnK%aveAbJn;n=wdm2g`R_R;y(B!^H8*c;Y|osv&*Xet=B)b7*G%7Bm){3i1TOL`0E~CD)qcN%a*XenDnvxCXbyYSFA*(P;MFU}viC+lW}tIOCscE3K~k z@-OJPqNZA7AE{m)lbI;mpOdlgxiRZz`3`l7y?)jc>}Osn?Q{QMzLTZ2PkHGj&5okq z-(JgHe_wciw}$lUVwsnbIeoShbP0l+XkK Da+LVP literal 0 HcmV?d00001 diff --git a/appdb/Other/Images.xcassets/wishes.imageset/Untitled@2x.png b/appdb/Other/Images.xcassets/wishes.imageset/Untitled@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c5a57b5b8b17b2210d12ec4647ce123258963fbf GIT binary patch literal 1287 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 literal 0 HcmV?d00001 diff --git "a/appdb/Resources/\342\200\242 AdaptiveUIAlertController/AdaptiveUIAlertController.swift" "b/appdb/Resources/\342\200\242 AdaptiveUIAlertController/AdaptiveUIAlertController.swift" index d1ca1d7b..60b16a9e 100644 --- "a/appdb/Resources/\342\200\242 AdaptiveUIAlertController/AdaptiveUIAlertController.swift" +++ "b/appdb/Resources/\342\200\242 AdaptiveUIAlertController/AdaptiveUIAlertController.swift" @@ -9,7 +9,7 @@ import Foundation import UIKit -// An adaptive UIAlertController that can adapt to light and dark themes +// An adaptive UIAlertController (iOS >= 11 && iOS < 13) that can adapt to light and dark themes // Source: https://stackoverflow.com/a/41780021 extension UIAlertController { diff --git "a/appdb/Resources/\342\200\242 AskBundleBeforeUploadView/AskBundleBeforeUploadViewController.swift" "b/appdb/Resources/\342\200\242 AskBundleBeforeUploadView/AskBundleBeforeUploadViewController.swift" index 6c9e15d4..1e3d1acd 100644 --- "a/appdb/Resources/\342\200\242 AskBundleBeforeUploadView/AskBundleBeforeUploadViewController.swift" +++ "b/appdb/Resources/\342\200\242 AskBundleBeforeUploadView/AskBundleBeforeUploadViewController.swift" @@ -76,7 +76,7 @@ class AskBundleBeforeUploadViewController: TableViewController { ["initialText": self.newBundleId, "callback": { [unowned self] (newBundleId: String) in self.newBundleId = newBundleId self.setUploadButtonEnabled() - }, "title": "New bundle id".localized(), "subtitle": "Tap to generate random".localized()] // todo localize + }, "title": "New bundle id".localized(), "subtitle": "Tap to generate random".localized()] ), Row(text: "Overwrite file".localized(), accessory: .switchToggle(value: overwriteFile) { [unowned self] newValue in self.overwriteFile = newValue diff --git "a/appdb/Resources/\342\200\242 LoadingTableView/LoadingTableView.swift" "b/appdb/Resources/\342\200\242 LoadingTableView/LoadingTableView.swift" index 5da8ab1e..e195e871 100644 --- "a/appdb/Resources/\342\200\242 LoadingTableView/LoadingTableView.swift" +++ "b/appdb/Resources/\342\200\242 LoadingTableView/LoadingTableView.swift" @@ -88,11 +88,16 @@ class LoadingTableView: UITableViewController { setConstraints(.loading) - adMobAdjustContentInsetsIfNeeded() + // Wishes are presented modally so you don't need to set bottom insets + if !(self is NewWishes) && !(self is FulfilledWishes) { + adMobAdjustContentInsetsIfNeeded() + } adChangeObservation = defaults.observe(.adBannerHeight) { [weak self] _ in guard let self = self else { return } - self.adMobAdjustContentInsetsIfNeeded() + if !(self is NewWishes) && !(self is FulfilledWishes) { + self.adMobAdjustContentInsetsIfNeeded() + } } } diff --git a/appdb/Startup/Extensions.swift b/appdb/Startup/Extensions.swift index cf8d2cfd..558beade 100644 --- a/appdb/Startup/Extensions.swift +++ b/appdb/Startup/Extensions.swift @@ -55,6 +55,13 @@ extension String { case "NOT_READY": return "The request timed out".localized() case "NOT_COMPATIBLE_WITH_DEVICE": return "Your device is not compatible with this app".localized() case "REPORT_ALREADY_SUBMITTED": return "A report has already been submitted".localized() + case "ERROR_PRO_REQUIRED": return "Your device needs to be PRO in order to request paid apps to be uploaded automatically.".localized() + case "ERROR_PAID_APPS_REQUESTS_LIMIT_REACHED": return "Paid apps request limit reached. You can request up to 1 paid apps per week.".localized() + case "ERROR_REQUEST_APPLE_APP": return "This app can not be requested, it is native iOS/iPadOS/tvOS app.".localized() + case "ERROR_REQUEST_ONLY_IOS_APPS_SUPPORTED": return "Only iOS Apps From AppStore are supported in automatic requests.".localized() + case "ERROR_REQUEST_STATUS_CANT_BE_SET": return "This request status can't be set.".localized() + case "ERROR_SUCH_REQUEST_EXISTS": return "Such request already exists. But we have added you as requester as well.".localized() + case "ERROR_SUCH_VERSION_EXISTS_AND_LINKS_AVAILABLE": return "Such version is already on appdb and it has links available.".localized() case "JSON could not be serialized because of error:\nThe data couldn’t be read because it isn’t in the correct format.": return "An error has occurred: malformed JSON".localized() default: return self.localized() } diff --git a/appdb/Tabs/Featured/Featured/Featured.swift b/appdb/Tabs/Featured/Featured/Featured.swift index 836a4183..5cf96d1b 100644 --- a/appdb/Tabs/Featured/Featured/Featured.swift +++ b/appdb/Tabs/Featured/Featured/Featured.swift @@ -49,6 +49,12 @@ class Featured: LoadingTableView, UIPopoverPresentationControllerDelegate { navigationItem.leftBarButtonItem = categoriesButton navigationItem.leftBarButtonItem?.isEnabled = false + // Add wishes button + + let wishesButton = UIBarButtonItem(image: UIImage(named: "wishes"), style: .plain, target: self, action: #selector(self.openWishes)) + navigationItem.rightBarButtonItem = wishesButton + navigationItem.rightBarButtonItem?.isEnabled = false + // Fix random separator margin issues if #available(iOS 9, *) { tableView.cellLayoutMarginsFollowReadableWidth = false } @@ -58,6 +64,9 @@ class Featured: LoadingTableView, UIPopoverPresentationControllerDelegate { // Enable categories button self.navigationItem.leftBarButtonItem?.isEnabled = true + + // Enable wishes button + self.navigationItem.rightBarButtonItem?.isEnabled = true }) // Wait for data to be fetched, reload tableView on completion @@ -125,6 +134,9 @@ class Featured: LoadingTableView, UIPopoverPresentationControllerDelegate { // Enable categories button self.navigationItem.leftBarButtonItem?.isEnabled = true + + // Enable wishes button + self.navigationItem.rightBarButtonItem?.isEnabled = true }) for cell in self.cells.compactMap({$0 as? ItemCollection}) { cell.requestItems() } @@ -150,6 +162,18 @@ class Featured: LoadingTableView, UIPopoverPresentationControllerDelegate { override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return state == .done ? cells[indexPath.row].height : 0 } + + // Open wishes + @objc func openWishes(_ sender: AnyObject) { + let wishesController = Wishes() + if Global.isIpad { + let nav = DismissableModalNavController(rootViewController: wishesController) + nav.modalPresentationStyle = .formSheet + self.navigationController?.present(nav, animated: true) + } else { + self.navigationController?.present(UINavigationController(rootViewController: wishesController), animated: true) + } + } } //////////////////////////////// diff --git a/appdb/Tabs/Featured/Wishes/Cells/WishAppCell.swift b/appdb/Tabs/Featured/Wishes/Cells/WishAppCell.swift new file mode 100644 index 00000000..db139664 --- /dev/null +++ b/appdb/Tabs/Featured/Wishes/Cells/WishAppCell.swift @@ -0,0 +1,110 @@ +// +// WishAppCell.swift +// appdb +// +// Created by ned on 26/07/2019. +// Copyright © 2019 ned. All rights reserved. +// + +import UIKit +import Cartography + +class WishAppCell: UITableViewCell { + + var nameLabel: UILabel! + var infoLabel: UILabel! + var statusLabel: UILabel! + var icon: UIImageView! + + func configure(with app: WishApp) { + nameLabel.text = app.name + infoLabel.text = "Price: %@".localizedFormat(app.price) + Global.bulletPoint + "Version: %@".localizedFormat(app.version) + if app.status == .new { + statusLabel.text = "↑\(app.requestersAmount)" + Global.bulletPoint + app.statusChangedAt + } else { + statusLabel.text = app.status.prettified + Global.bulletPoint + app.statusChangedAt + } + if let url = URL(string: app.image) { + icon.af_setImage(withURL: url, placeholderImage: #imageLiteral(resourceName: "placeholderIcon"), + filter: Global.roundedFilter(from: 80 ~~ 60), + imageTransition: .crossDissolve(0.2)) + } + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: .default, reuseIdentifier: reuseIdentifier) + + setup() + setConstraints() + } + + private func setup() { + + //UI + theme_backgroundColor = Color.veryVeryLightGray + setBackgroundColor(Color.veryVeryLightGray) + let bgColorView = UIView() + bgColorView.theme_backgroundColor = Color.cellSelectionColor + selectedBackgroundView = bgColorView + accessoryType = .disclosureIndicator + + // Name + nameLabel = UILabel() + nameLabel.theme_textColor = Color.title + nameLabel.font = .systemFont(ofSize: 15 ~~ 14) + nameLabel.numberOfLines = 1 + nameLabel.makeDynamicFont() + + // Info Label + infoLabel = UILabel() + infoLabel.theme_textColor = Color.darkGray + infoLabel.font = .systemFont(ofSize: 13 ~~ 12) + infoLabel.numberOfLines = 1 + infoLabel.makeDynamicFont() + + // Status Label + statusLabel = UILabel() + statusLabel.theme_textColor = Color.darkGray + statusLabel.font = .systemFont(ofSize: 13 ~~ 12) + statusLabel.numberOfLines = 1 + statusLabel.makeDynamicFont() + + // Icon + icon = UIImageView() + icon.layer.borderWidth = 1 / UIScreen.main.scale + icon.layer.theme_borderColor = Color.borderCgColor + + icon.layer.cornerRadius = Global.cornerRadius(from: (80 ~~ 60)) + + contentView.addSubview(nameLabel) + contentView.addSubview(infoLabel) + contentView.addSubview(statusLabel) + contentView.addSubview(icon) + } + + // Set constraints + private func setConstraints() { + constrain(icon, nameLabel, infoLabel, statusLabel) { icon, name, info, status in + icon.width ~== (80 ~~ 60) + icon.height ~== icon.width + icon.leading ~== icon.superview!.layoutMarginsGuide.leading + icon.centerY ~== icon.superview!.centerY + + name.leading ~== icon.trailing ~+ (15 ~~ 12) + name.trailing ~== name.superview!.trailing ~- Global.Size.margin.value + name.centerY ~== name.superview!.centerY ~- (22 ~~ 20) + + info.top ~== name.bottom ~+ (5 ~~ 4) + info.leading ~== name.leading + info.trailing ~== name.trailing + + status.leading ~== info.leading + status.trailing ~<= status.superview!.trailing ~- Global.Size.margin.value + status.top ~== info.bottom ~+ (5 ~~ 4) + } + } +} diff --git a/appdb/Tabs/Featured/Wishes/Fulfilled/FulfilledWishes.swift b/appdb/Tabs/Featured/Wishes/Fulfilled/FulfilledWishes.swift new file mode 100644 index 00000000..a8f31bb6 --- /dev/null +++ b/appdb/Tabs/Featured/Wishes/Fulfilled/FulfilledWishes.swift @@ -0,0 +1,118 @@ +// +// FulfilledWishes.swift +// appdb +// +// Created by ned on 07/07/2019. +// Copyright © 2019 ned. All rights reserved. +// + +import UIKit + +class FulfilledWishes: LoadingTableView { + + private var currentPage: Int = 1 + private var allLoaded: Bool = false + private var items: [WishApp] = [] + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.register(WishAppCell.self, forCellReuseIdentifier: "cell") + tableView.rowHeight = (105 ~~ 85) + + tableView.theme_separatorColor = Color.borderColor + tableView.theme_backgroundColor = Color.tableViewBackgroundColor + view.theme_backgroundColor = Color.tableViewBackgroundColor + + animated = false + showsErrorButton = false + showsSpinner = false + + // todo 3d touch + + // Hide the 'Back' text on back button + let backItem = UIBarButtonItem(title: "", style: .done, target: nil, action: nil) + navigationItem.backBarButtonItem = backItem + + // Hide last separator + tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 1)) + + // Refresh action + tableView.spr_setIndicatorHeader { [weak self] in + self?.currentPage = 1 + self?.items = [] + self?.loadContent() + } + + setFooter() + + // Begin refresh + tableView.spr_beginRefreshing() + } + + // Called when user reaches bottom, loads 25 more + private func setFooter() { + tableView.spr_setIndicatorFooter { [weak self] in + self?.currentPage += 1 + self?.loadContent() + } + } + + private func loadContent() { + + if self.allLoaded { + allLoaded = false + setFooter() + } + + API.getPublishRequests(includeAll: true, page: currentPage, success: { [weak self] array in + guard let self = self else { return } + + let results = array.filter({ $0.status != .new }) + + if results.isEmpty { + self.tableView.spr_endRefreshingWithNoMoreData() + self.allLoaded = true + if self.currentPage == 1 { + delay(0.3) { + self.tableView.spr_endRefreshingWithNoMoreData() + self.showErrorMessage(text: "No fulfilled wishes to show".localized(), animated: false) + } + } else { + self.tableView.spr_endRefreshingWithNoMoreData() + self.state = .done + } + } else { + self.items += results + self.tableView.spr_endRefreshing() + self.state = .done + } + }, fail: { error in + self.tableView.spr_endRefreshing() + self.items = [] + self.tableView.reloadData() + self.showErrorMessage(text: "Cannot connect".localized(), secondaryText: error, animated: false) + }) + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return items.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? WishAppCell, items.indices.contains(indexPath.row) else { return UITableViewCell() } + cell.configure(with: items[indexPath.row]) + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + let vc = Details(type: .ios, trackid: items[indexPath.row].trackid) + navigationController?.pushViewController(vc, animated: true) + } +} diff --git a/appdb/Tabs/Featured/Wishes/New/NewWishes.swift b/appdb/Tabs/Featured/Wishes/New/NewWishes.swift new file mode 100644 index 00000000..35318de5 --- /dev/null +++ b/appdb/Tabs/Featured/Wishes/New/NewWishes.swift @@ -0,0 +1,135 @@ +// +// NewWishes.swift +// appdb +// +// Created by ned on 07/07/2019. +// Copyright © 2019 ned. All rights reserved. +// + +import UIKit + +class NewWishes: LoadingTableView { + + private var currentPage: Int = 1 + private var allLoaded: Bool = false + private var items: [WishApp] = [] + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.register(WishAppCell.self, forCellReuseIdentifier: "cell") + tableView.rowHeight = (105 ~~ 85) + + tableView.theme_separatorColor = Color.borderColor + tableView.theme_backgroundColor = Color.tableViewBackgroundColor + view.theme_backgroundColor = Color.tableViewBackgroundColor + + animated = false + showsErrorButton = false + showsSpinner = false + + // todo 3d touch + + // Hide the 'Back' text on back button + let backItem = UIBarButtonItem(title: "", style: .done, target: nil, action: nil) + navigationItem.backBarButtonItem = backItem + + // Hide last separator + tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 1)) + + // Refresh action + tableView.spr_setIndicatorHeader { [weak self] in + self?.currentPage = 1 + self?.items = [] + self?.loadContent() + } + + setFooter() + + // Begin refresh + tableView.spr_beginRefreshing() + } + + // Called when user reaches bottom, loads 25 more + private func setFooter() { + tableView.spr_setIndicatorFooter { [weak self] in + self?.currentPage += 1 + self?.loadContent() + } + } + + private func loadContent() { + + if self.allLoaded { + allLoaded = false + setFooter() + } + + API.getPublishRequests(includeAll: false, page: currentPage, success: { [weak self] array in + guard let self = self else { return } + + if array.isEmpty { + self.allLoaded = true + if self.currentPage == 1 { + delay(0.3) { + self.tableView.spr_endRefreshingWithNoMoreData() + self.showErrorMessage(text: "No new wishes to show".localized(), animated: false) + } + } else { + self.tableView.spr_endRefreshingWithNoMoreData() + self.state = .done + } + } else { + self.items += array + if self.items.count < 25 { + self.tableView.spr_endRefreshingWithNoMoreData() + } else { + self.tableView.spr_endRefreshing() + } + self.state = .done + } + }, fail: { error in + self.tableView.spr_endRefreshing() + self.items = [] + self.tableView.reloadData() + self.showErrorMessage(text: "Cannot connect".localized(), secondaryText: error, animated: false) + }) + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return items.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? WishAppCell, items.indices.contains(indexPath.row) else { return UITableViewCell() } + cell.configure(with: items[indexPath.row]) + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + let vc = Details(type: .ios, trackid: items[indexPath.row].trackid) + navigationController?.pushViewController(vc, animated: true) + } + + override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + return true + } + + override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { + let upvote = UITableViewRowAction(style: .normal, title: "↑") { _, _ in + API.createPublishRequest(appStoreUrl: "https://apps.apple.com/app/id\(self.items[indexPath.row].trackid)") { error in + if let error = error { + Messages.shared.showError(message: error.prettified, context: Global.isIpad ? .viewController(self) : nil) + } + } + } + upvote.backgroundColor = .systemOrange + return [upvote] + } +} diff --git a/appdb/Tabs/Featured/Wishes/Wishes.swift b/appdb/Tabs/Featured/Wishes/Wishes.swift new file mode 100644 index 00000000..559e3180 --- /dev/null +++ b/appdb/Tabs/Featured/Wishes/Wishes.swift @@ -0,0 +1,200 @@ +// +// Wishes.swift +// appdb +// +// Created by ned on 07/07/2019. +// Copyright © 2019 ned. All rights reserved. +// + +import UIKit +import Cartography + +class Wishes: UIViewController { + + var currentViewController: UIViewController? + + var headerView: ILTranslucentView! + var control: UISegmentedControl! + var line: UIView! + + // Constraints group, will be replaced when orientation changes + var group = ConstraintGroup() + + lazy var viewControllersArray: [UIViewController] = { + [NewWishes(), FulfilledWishes()] + }() + + override func viewDidLoad() { + super.viewDidLoad() + + // Hide bottom hairline + if let nav = navigationController { nav.navigationBar.hideBottomHairline() } + + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done".localized(), style: .done, target: self, action: #selector(self.dismissAnimated)) + navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(self.addTapped)) + + headerView = ILTranslucentView(frame: .zero) + headerView.translucentAlpha = 1 + + control = UISegmentedControl(items: ["New".localized(), "Fulfilled".localized()]) + control.addTarget(self, action: #selector(self.indexDidChange), for: .valueChanged) + control.selectedSegmentIndex = 0 + + line = UIView(frame: .zero) + line.theme_backgroundColor = Color.borderColor + + headerView.addSubview(line) + headerView.addSubview(control) + view.addSubview(headerView) + + // UI + view.theme_backgroundColor = Color.tableViewBackgroundColor + title = "Wishes".localized() + + // Set constraints + setConstraints() + + // Add first view controller + currentViewController = viewControllersArray[0] + addChild(currentViewController!) + addSubview(currentViewController!.view) + } + + // MARK: - Constraints + + private func setConstraints() { + constrain(headerView, control, line, replace: group) { header, control, line in + + header.top ~== header.superview!.topMargin + header.left ~== header.superview!.left + header.right ~== header.superview!.right + header.height ~== 40 + + line.height ~== 1 / UIScreen.main.scale + line.left ~== header.left + line.right ~== header.right + line.top ~== header.bottom ~- 0.5 + + control.top ~== header.top + control.centerX ~== header.centerX + control.width ~== (300 ~~ 260) + } + } + + // Update constraints to reflect orientation change (recalculate navigationBar + statusBar height) + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + coordinator.animate(alongsideTransition: { (_: UIViewControllerTransitionCoordinatorContext!) -> Void in + guard self.headerView != nil else { return } + self.setConstraints() + }, completion: nil) + } + + // MARK: - Segmented Control index did change + + // Switch table view based on segment index + @objc func indexDidChange(sender: UISegmentedControl) { + let new: UIViewController = viewControllersArray[sender.selectedSegmentIndex] + self.cycle(from: self.currentViewController!, to: new) + self.currentViewController = new + } + + @objc func dismissAnimated() { + dismiss(animated: true) + } +} + +extension Wishes { + + // Switch between table views with fade animation + func cycle(from old: UIViewController, to new: UIViewController) { + control.isUserInteractionEnabled = false + old.willMove(toParent: nil) + self.addChild(new) + self.addSubview(new.view) + new.view.alpha = 0 + new.view.layoutIfNeeded() + + UIView.animate(withDuration: 0.2, animations: { + new.view.alpha = 1 + old.view.alpha = 0 + }, completion: { _ in + old.view.removeFromSuperview() + old.removeFromParent() + new.didMove(toParent: self) + self.control.isUserInteractionEnabled = true + }) + } + + // Add subview and constraints + func addSubview(_ subview: UIView) { + view.addSubview(subview) + constrain(view, subview, headerView) { view, subview, header in + subview.top ~== header.bottom + subview.bottom ~== view.bottom + subview.right ~== view.right + subview.left ~== view.left + } + } +} + +// MARK: - Request AppStore app for automatic cracking + +extension Wishes { + @objc private func addTapped() { + let alert = UIAlertController(title: "Enter AppStore URL".localized(), message: "Enter below the AppStore URL of the app you'd like to request".localized(), preferredStyle: .alert, adaptive: true) + alert.addTextField(configurationHandler: { textField in + textField.addTarget(self, action: #selector(self.urlTextChanged), for: .editingChanged) + textField.placeholder = "https://apps.apple.com/us/app/...".localized() + textField.keyboardType = .URL + textField.theme_keyboardAppearance = [.light, .dark, .dark] + textField.clearButtonMode = .whileEditing + }) + + alert.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel)) + + let load = UIAlertAction(title: "Add".localized(), style: .default, handler: { _ in + guard var text = alert.textFields?[0].text else { return } + if !text.hasPrefix("http://"), !text.hasPrefix("https://") { + text = "https://" + text + } + guard let url = URL(string: text) else { return } + API.createPublishRequest(appStoreUrl: url.absoluteString) { error in + if let error = error { + Messages.shared.showError(message: error, context: Global.isIpad ? .viewController(self) : nil) + } else { + Messages.shared.showSuccess(message: "App has been requested successfully!".localized(), context: Global.isIpad ? .viewController(self) : nil) + } + } + }) + + alert.addAction(load) + load.isEnabled = false + + DispatchQueue.main.async { + self.present(alert, animated: true) + } + } + + @objc func urlTextChanged(sender: UITextField) { + var responder: UIResponder = sender + while !(responder is UIAlertController) { responder = responder.next! } + if let alert = responder as? UIAlertController { + // Enable 'Load' button if text input is a valid url + if let text = sender.text, isValidUrl(urlString: text) { + (alert.actions[1] as UIAlertAction).isEnabled = true + } else { + (alert.actions[1] as UIAlertAction).isEnabled = false + } + } + } + + func isValidUrl(urlString: String) -> Bool { + let types: NSTextCheckingResult.CheckingType = [.link] + let detector = try? NSDataDetector(types: types.rawValue) + guard detector != nil && !urlString.isEmpty else { return false } + return detector!.numberOfMatches(in: urlString, options: NSRegularExpression.MatchingOptions(rawValue: 0), + range: NSRange(location: 0, length: urlString.count)) > 0 + } +} diff --git a/appdb/Tabs/Settings/Theme Chooser/ThemeChooser.swift b/appdb/Tabs/Settings/Theme Chooser/ThemeChooser.swift index 7731c610..8766a32d 100644 --- a/appdb/Tabs/Settings/Theme Chooser/ThemeChooser.swift +++ b/appdb/Tabs/Settings/Theme Chooser/ThemeChooser.swift @@ -76,7 +76,7 @@ class ThemeChooser: UITableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if #available(iOS 13.0, *), indexPath.section > 0 { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) - cell.textLabel?.text = "Follow System Appearance".localized() // todo localize + cell.textLabel?.text = "Follow System Appearance".localized() cell.textLabel?.makeDynamicFont() cell.textLabel?.theme_textColor = Color.title cell.setBackgroundColor(Color.veryVeryLightGray) @@ -117,13 +117,18 @@ class ThemeChooser: UITableViewController { } } + override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + if #available(iOS 13.0, *), section > 0 { + return "Automatically switch between light and dark theme based on System Appearance. To switch to the Darker theme instead, just manually select it once.\n\nNOTE: If you're experiencing issues (theme not switching automatically or mixed themes) just close the app from multitasking and reopen it.".localized() + } + return nil + } + @objc func appearanceToggleValueChanged(sender: UISwitch) { Preferences.set(.followSystemAppearance, to: sender.isOn) disableUserInteractionIfNeeded() - debugLog("system appearance: \(Global.isDarkSystemAppearance ? "Dark" : "Light")") - if Preferences.followSystemAppearance { switch Themes.current { case .dark, .darker: @@ -136,6 +141,8 @@ class ThemeChooser: UITableViewController { } } } + + Global.refreshAppearanceForCurrentTheme() } func disableUserInteractionIfNeeded() { @@ -153,7 +160,6 @@ class ThemeChooser: UITableViewController { Themes.switchTo(theme: theme) changedThemeDelegate?.changedTheme() tableView.reloadData() - Global.refreshAppearanceForCurrentTheme() } } } diff --git a/appdb/en.lproj/Localizable.strings b/appdb/en.lproj/Localizable.strings index 7bae37e8..418585e8 100644 --- a/appdb/en.lproj/Localizable.strings +++ b/appdb/en.lproj/Localizable.strings @@ -254,6 +254,8 @@ "Email" = "Email"; "Telegram" = "Telegram"; "Could not find email service." = "Could not find email service."; +"Follow System Appearance" = "Follow System Appearance"; +"Automatically switch between light and dark theme based on System Appearance. To switch to the Darker theme instead, just manually select it once.\n\nNOTE: If you're experiencing issues (theme not switching automatically or mixed themes) just close the app from multitasking and reopen it." = "Automatically switch between light and dark theme based on System Appearance. To switch to the Darker theme instead, just manually select it once.\n\nNOTE: If you're experiencing issues (theme not switching automatically or mixed themes) just close the app from multitasking and reopen it."; ///////////////////// // UPDATES // @@ -339,6 +341,24 @@ "Icon and banner designer" = "Icon and banner designer"; "View project on GitHub" = "View project on GitHub"; +///////////////////////////////////////// +// WISHES / AUTOMATIC REQUESTS // +///////////////////////////////////////// +"Wishes" = "Wishes"; +"New" = "New"; +"Processing" = "Processing"; +"Failed" = "Failed"; +"Fulfilled" = "Fulfilled"; +"Free" = "Free"; +"Enter AppStore URL" = "Enter AppStore URL"; +"Enter below the AppStore URL of the app you'd like to request" = "Enter below the AppStore URL of the app you'd like to request"; +"Add" = "Add"; +"App has been requested successfully!" = "App has been requested successfully!"; +"Price: %@" = "Price: %@"; +"Version: %@" = "Version: %@"; +"No new wishes to show" = "No new wishes to show"; +"No fulfilled wishes to show" = "No fulfilled wishes to show"; + /////////////////// // OTHER // /////////////////// diff --git a/appdb/es.lproj/Localizable.strings b/appdb/es.lproj/Localizable.strings index 1b7d82b0..398a3bc7 100644 --- a/appdb/es.lproj/Localizable.strings +++ b/appdb/es.lproj/Localizable.strings @@ -254,6 +254,8 @@ "Email" = "Correo"; "Telegram" = "Telegram"; "Could not find email service." = "Imposible de encontrar el servicio de correo."; +"Follow System Appearance" = "Seguir diseño del sistema"; +"Automatically switch between light and dark theme based on System Appearance. To switch to the Darker theme instead, just manually select it once.\n\nNOTE: If you're experiencing issues (theme not switching automatically or mixed themes) just close the app from multitasking and reopen it." = "Automáticamente cambiar la apar de claro o a oscuro dependiendo del sistema. Para cambiar al tema más oscuro, simplemente selecciónelo manualmente una vez.\n\nNOTA: Si estás experimentando errores (el tema no se cambia correctamente o los temas se mezclan) cierra la app en segundo plano y vuélvelo a abrir."; ///////////////////// // UPDATES // @@ -339,6 +341,25 @@ "Icon and banner designer" = "Diseñador del icono y del banner"; "View project on GitHub" = "Ver proyecto en Github"; +///////////////////////////////////////// +// WISHES / AUTOMATIC REQUESTS // +///////////////////////////////////////// +"Wishes" = "Pedidos"; +"New" = "Nuevo"; +"Processing" = "Procesando"; +"Failed" = "Fallado"; +"Fulfilled" = "Completado"; +"Free" = "Gratis"; +"Enter AppStore URL" = "Introducir encale del AppStore"; +"Enter below the AppStore URL of the app you'd like to request" = "Introduce el enlace de app que quieres pedir "; +"Add" = "Añadir"; +"App has been requested successfully!" = "¡Aplicación pedida correctamente!"; +"Price: %@" = "Precio: %@"; +"Version: %@" = "Versión: %@"; +"No new wishes to show" = "No hay nuevos pedidos para mostrar"; +"No fulfilled wishes to show" = "Mo hay pedidos completados para mostrar"; +"No new wishes to show" = "No hay pedidos para mostrar"; + /////////////////// // OTHER // /////////////////// diff --git a/appdb/it.lproj/Localizable.strings b/appdb/it.lproj/Localizable.strings index 15e9158c..ef12ce66 100644 --- a/appdb/it.lproj/Localizable.strings +++ b/appdb/it.lproj/Localizable.strings @@ -254,6 +254,8 @@ "Email" = "Email"; "Telegram" = "Telegram"; "Could not find email service." = "Impossibile trovare il servizio email collegato."; +"Follow System Appearance" = "Segui tema del sistema"; +"Automatically switch between light and dark theme based on System Appearance. To switch to the Darker theme instead, just manually select it once.\n\nNOTE: If you're experiencing issues (theme not switching automatically or mixed themes) just close the app from multitasking and reopen it." = "Passa automaticamente tra tema scuro e chiaro in base alle impostazioni di sistema. Per passare al tema più scuro, invece, selezionalo manualmente una volta.\n\nNOTA: Se rilevi dei problemi (tema che non cambia in automatico, oppure temi chiaro e scuro mischiati) chiudi l'app dal multitasking e riaprila."; ///////////////////// // UPDATES // @@ -339,6 +341,24 @@ "Icon and banner designer" = "Designer di icona e banner"; "View project on GitHub" = "Vedi progetto su GitHub"; +///////////////////////////////////////// +// WISHES / AUTOMATIC REQUESTS // +///////////////////////////////////////// +"Wishes" = "Desideri"; +"New" = "Nuovo"; +"Processing" = "In elaborazione"; +"Failed" = "Fallito"; +"Fulfilled" = "Aggiunto"; +"Free" = "Gratis"; +"Enter AppStore URL" = "Inserisci URL AppStore"; +"Enter below the AppStore URL of the app you'd like to request" = "Inserisci qui l'URL AppStore dell'app che vorrestu richiedere"; +"Add" = "Aggiungi"; +"App has been requested successfully!" = "App richiesta con successo!"; +"Price: %@" = "Prezzo: %@"; +"Version: %@" = "Versione: %@"; +"No new wishes to show" = "Nessun nuovo desiderio da mostrare"; +"No fulfilled wishes to show" = "Nessun desiderio esaurito da mostrare"; + /////////////////// // OTHER // /////////////////// diff --git a/appdb/ru.lproj/Localizable.strings b/appdb/ru.lproj/Localizable.strings index 5ebaaca9..09b1ca7a 100644 --- a/appdb/ru.lproj/Localizable.strings +++ b/appdb/ru.lproj/Localizable.strings @@ -254,6 +254,8 @@ "Email" = "Email"; "Telegram" = "Telegram"; "Could not find email service." = "Не могу найти сервис почты."; +"Follow System Appearance" = "Как у системы"; +"Automatically switch between light and dark theme based on System Appearance. To switch to the Darker theme instead, just manually select it once.\n\nNOTE: If you're experiencing issues (theme not switching automatically or mixed themes) just close the app from multitasking and reopen it." = "Автоматически переключать тему в зависимости от настроек системы. Чтобы вручную указать тему, просто выберите ее однажды.\n\nПРИМЕЧАНИЕ: Если вы испытываете проблемы с переключением тем, просто закройте приложение из меню многозадачности и откройте его снова."; ///////////////////// // UPDATES // @@ -339,6 +341,24 @@ "Icon and banner designer" = "Дизайнер иконки и баннера"; "View project on GitHub" = "Посмотреть исходный код GitHub"; +///////////////////////////////////////// +// WISHES / AUTOMATIC REQUESTS // +///////////////////////////////////////// +"Wishes" = "Желания"; +"New" = "Новое"; +"Processing" = "Подождите..."; +"Failed" = "Неудача"; +"Fulfilled" = "Выполнено"; +"Free" = "Бесплатно"; +"Enter AppStore URL" = "Введите ссылку на AppStore"; +"Enter below the AppStore URL of the app you'd like to request" = "Введите ниже URL адрес приложения в AppStore (кнопка поделиться)"; +"Add" = "Добавить"; +"App has been requested successfully!" = "Ваш запрос отправлен!"; +"Price: %@" = "Цена: %@"; +"Version: %@" = "Версия: %@"; +"No new wishes to show" = "Нет новых желаний"; +"No fulfilled wishes to show" = "Нет выполненных желаний"; + /////////////////// // OTHER // ///////////////////