custom explore page + api
This commit is contained in:
1 parent
5a94ea73d9
commit
4d6b093ffd
10 files changed
+415
-49
No files matched your search
@@ -9,6 +9,7 @@
|
|||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
6F09B7BB2DEB873F00CDE89C /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 6F09B7BA2DEB873F00CDE89C /* ZIPFoundation */; };
|
6F09B7BB2DEB873F00CDE89C /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 6F09B7BA2DEB873F00CDE89C /* ZIPFoundation */; };
|
||||||
6F09BA332E00808300CDE89C /* Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 6F09BA322E00808300CDE89C /* Dynamic */; };
|
6F09BA332E00808300CDE89C /* Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 6F09BA322E00808300CDE89C /* Dynamic */; };
|
||||||
|
6FB105552E26D3EC00185A5C /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = 6FB105542E26D3EC00185A5C /* CachedAsyncImage */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
6FB105552E26D3EC00185A5C /* CachedAsyncImage in Frameworks */,
|
||||||
6F09BA332E00808300CDE89C /* Dynamic in Frameworks */,
|
6F09BA332E00808300CDE89C /* Dynamic in Frameworks */,
|
||||||
6F09B7BB2DEB873F00CDE89C /* ZIPFoundation in Frameworks */,
|
6F09B7BB2DEB873F00CDE89C /* ZIPFoundation in Frameworks */,
|
||||||
);
|
);
|
||||||
@@ -77,6 +79,7 @@
|
|||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
6F09B7BA2DEB873F00CDE89C /* ZIPFoundation */,
|
6F09B7BA2DEB873F00CDE89C /* ZIPFoundation */,
|
||||||
6F09BA322E00808300CDE89C /* Dynamic */,
|
6F09BA322E00808300CDE89C /* Dynamic */,
|
||||||
|
6FB105542E26D3EC00185A5C /* CachedAsyncImage */,
|
||||||
);
|
);
|
||||||
productName = "Pocket Poster";
|
productName = "Pocket Poster";
|
||||||
productReference = 6F09B79D2DEB694B00CDE89C /* Pocket Poster.app */;
|
productReference = 6F09B79D2DEB694B00CDE89C /* Pocket Poster.app */;
|
||||||
@@ -109,6 +112,7 @@
|
|||||||
packageReferences = (
|
packageReferences = (
|
||||||
6F09B7B92DEB873F00CDE89C /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
|
6F09B7B92DEB873F00CDE89C /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
|
||||||
6F09BA312E00808300CDE89C /* XCRemoteSwiftPackageReference "Dynamic" */,
|
6F09BA312E00808300CDE89C /* XCRemoteSwiftPackageReference "Dynamic" */,
|
||||||
|
6FB105532E26D3EC00185A5C /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 77;
|
preferredProjectObjectVersion = 77;
|
||||||
productRefGroup = 6F09B79E2DEB694B00CDE89C /* Products */;
|
productRefGroup = 6F09B79E2DEB694B00CDE89C /* Products */;
|
||||||
@@ -366,6 +370,14 @@
|
|||||||
minimumVersion = 1.2.0;
|
minimumVersion = 1.2.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
6FB105532E26D3EC00185A5C /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/lorenzofiamingo/swiftui-cached-async-image";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 2.1.1;
|
||||||
|
};
|
||||||
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
@@ -379,6 +391,11 @@
|
|||||||
package = 6F09BA312E00808300CDE89C /* XCRemoteSwiftPackageReference "Dynamic" */;
|
package = 6F09BA312E00808300CDE89C /* XCRemoteSwiftPackageReference "Dynamic" */;
|
||||||
productName = Dynamic;
|
productName = Dynamic;
|
||||||
};
|
};
|
||||||
|
6FB105542E26D3EC00185A5C /* CachedAsyncImage */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 6FB105532E26D3EC00185A5C /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */;
|
||||||
|
productName = CachedAsyncImage;
|
||||||
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = 6F09B7952DEB694B00CDE89C /* Project object */;
|
rootObject = 6F09B7952DEB694B00CDE89C /* Project object */;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "b781b75a76813cee1292db0b540a9eecd3df93961ced8a0063095503735dadb2",
|
"originHash" : "73a600cf677d3bb2b929c60349923a2a4e20e6a9ef10509bf043d2ad2229ca95",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "dynamic",
|
"identity" : "dynamic",
|
||||||
@@ -10,6 +10,15 @@
|
|||||||
"version" : "1.2.0"
|
"version" : "1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftui-cached-async-image",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/lorenzofiamingo/swiftui-cached-async-image",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "467a3d17479887943ab917a379e62bbaff60ac8a",
|
||||||
|
"version" : "2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "zipfoundation",
|
"identity" : "zipfoundation",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
//
|
||||||
|
// CowabungaAPI.swift
|
||||||
|
// Pocket Poster
|
||||||
|
//
|
||||||
|
// Created by lemin on 7/15/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
enum FilterType: String, CaseIterable {
|
||||||
|
case random = "Random"
|
||||||
|
case newest = "Newest"
|
||||||
|
case oldest = "Oldest"
|
||||||
|
}
|
||||||
|
|
||||||
|
class CowabungaAPI: ObservableObject {
|
||||||
|
|
||||||
|
static let shared = CowabungaAPI()
|
||||||
|
|
||||||
|
var serverURL = ""
|
||||||
|
var session = URLSession.shared
|
||||||
|
|
||||||
|
func fetchWallpapers(type: DownloadableWallpaper.WallpaperType) async throws -> [DownloadableWallpaper] {
|
||||||
|
let request = URLRequest(url: .init(string: serverURL + "wallpapers-\(type.rawValue).json")!)
|
||||||
|
|
||||||
|
let (data, response) = try await session.data(for: request) as! (Data, HTTPURLResponse)
|
||||||
|
guard response.statusCode == 200 else { throw APIError.connectionFailed }
|
||||||
|
let wallpapers = try JSONDecoder().decode([DownloadableWallpaper].self, from: data)
|
||||||
|
|
||||||
|
for i in wallpapers.indices {
|
||||||
|
wallpapers[i].type = type
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallpapers
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterWallpapers(wallpapers: [DownloadableWallpaper], filterType: FilterType) -> [DownloadableWallpaper] {
|
||||||
|
var filtered = wallpapers
|
||||||
|
if filterType == FilterType.newest {
|
||||||
|
filtered = filtered.reversed()
|
||||||
|
} else if filterType == FilterType.random {
|
||||||
|
filtered = filtered.shuffled()
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCommitHash() async throws -> String {
|
||||||
|
let request = URLRequest(url: .init(string: serverURL + "https://api.github.com/repos/SerStars/nugget-wallpapers/commits/main")!)
|
||||||
|
|
||||||
|
let (data, response) = try await session.data(for: request) as! (Data, HTTPURLResponse)
|
||||||
|
guard response.statusCode == 200 else { throw APIError.connectionFailed }
|
||||||
|
guard let repoinfo = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { throw APIError.repoHashError }
|
||||||
|
guard let hash = repoinfo["sha"] as? String else { throw APIError.repoHashError }
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDownloadURLForWallpaper(wallpaper: DownloadableWallpaper) -> URL {
|
||||||
|
if wallpaper.url.hasPrefix("https://") {
|
||||||
|
URL(string: wallpaper.url)!
|
||||||
|
} else {
|
||||||
|
URL(string: serverURL + wallpaper.url)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPreviewURLForWallpaper(wallpaper: DownloadableWallpaper) -> URL {
|
||||||
|
URL(string: serverURL + wallpaper.preview)!
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
Task {
|
||||||
|
do {
|
||||||
|
let hash = try await getCommitHash()
|
||||||
|
serverURL = "https://raw.githubusercontent.com/SerStars/nugget-wallpapers/\(hash)/"
|
||||||
|
} catch {
|
||||||
|
await UIApplication.shared.alert(body: error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// DownloadableWallpaper.swift
|
||||||
|
// Pocket Poster
|
||||||
|
//
|
||||||
|
// Created by lemin on 7/15/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
class DownloadableWallpaper: Identifiable, Codable {
|
||||||
|
var name: String
|
||||||
|
var description: String
|
||||||
|
var url: String
|
||||||
|
var preview: String
|
||||||
|
var authors: String
|
||||||
|
var type: WallpaperType?
|
||||||
|
|
||||||
|
init(name: String, description: String, authors: String, preview: String, url: String, version: String) {
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.authors = authors
|
||||||
|
self.preview = preview
|
||||||
|
self.url = url
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WallpaperType: String, Codable {
|
||||||
|
case custom, apple, template
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// APIError.swift
|
||||||
|
// Pocket Poster
|
||||||
|
//
|
||||||
|
// Created by lemin on 7/15/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum APIError: LocalizedError {
|
||||||
|
// Throw when cannot connect to server
|
||||||
|
case connectionFailed
|
||||||
|
|
||||||
|
// Throw when it fails to get the repo hash
|
||||||
|
case repoHashError
|
||||||
|
|
||||||
|
// Throw in all other cases
|
||||||
|
case unexpected(info: String)
|
||||||
|
|
||||||
|
public var errorDescription: String? {
|
||||||
|
switch self {
|
||||||
|
case .connectionFailed:
|
||||||
|
return NSLocalizedString("Could not connect to server", comment: "")
|
||||||
|
case .repoHashError:
|
||||||
|
return NSLocalizedString("Unable to obtain repo hash. Maybe update to the latest version?", comment: "")
|
||||||
|
case .unexpected(let info):
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// URLCache+imageCache.swift
|
||||||
|
// Cowabunga
|
||||||
|
//
|
||||||
|
// Created by sourcelocation on 02/02/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CachedAsyncImage
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension URLCache {
|
||||||
|
|
||||||
|
static let imageCache = URLCache(memoryCapacity: 512*1000*1000, diskCapacity: 10*1000*1000*1000)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// CornerRadius++.swift
|
||||||
|
// Cowabunga
|
||||||
|
//
|
||||||
|
// Created by sourcelocation on 30/01/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
|
||||||
|
clipShape( RoundedCorner(radius: radius, corners: corners) )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RoundedCorner: Shape {
|
||||||
|
|
||||||
|
var radius: CGFloat = .infinity
|
||||||
|
var corners: UIRectCorner = .allCorners
|
||||||
|
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
|
||||||
|
return Path(path.cgPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
"sourceLanguage" : "en",
|
"sourceLanguage" : "en",
|
||||||
"strings" : {
|
"strings" : {
|
||||||
|
"" : {
|
||||||
|
|
||||||
|
},
|
||||||
"Actions" : {
|
"Actions" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ar" : {
|
"ar" : {
|
||||||
@@ -462,6 +465,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Apple" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Apply" : {
|
"Apply" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -1438,6 +1444,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Could not connect to server" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Could not download wallpaper!" : {
|
"Could not download wallpaper!" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -2210,6 +2219,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Custom" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Detect" : {
|
"Detect" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -4229,6 +4241,9 @@
|
|||||||
},
|
},
|
||||||
"Failed to fetch CarPlay wallpapers" : {
|
"Failed to fetch CarPlay wallpapers" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Failed to fetch wallpapers" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Failed to import tendies" : {
|
"Failed to import tendies" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -4537,9 +4552,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Failed to load explore page." : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Fallback Shortcut Creator" : {
|
"Fallback Shortcut Creator" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -4848,6 +4860,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Filter Wallpapers" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Find Wallpapers" : {
|
"Find Wallpapers" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -8749,9 +8764,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Retry" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Select Tendies" : {
|
"Select Tendies" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -10603,6 +10615,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Unable to obtain repo hash. Maybe update to the latest version?" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Update Available" : {
|
"Update Available" : {
|
||||||
"comment" : "app update available on GitHub",
|
"comment" : "app update available on GitHub",
|
||||||
|
|||||||
@@ -6,41 +6,209 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import CachedAsyncImage
|
||||||
|
|
||||||
|
let MIN_SIZE: CGFloat = 165
|
||||||
|
|
||||||
struct ExploreView: View {
|
struct ExploreView: View {
|
||||||
@State private var isLoading = true
|
@ObservedObject var cowabungaAPI = CowabungaAPI.shared
|
||||||
@State private var error: Error? = nil
|
|
||||||
|
|
||||||
#if DEBUG
|
// lazyvgrid
|
||||||
@AppStorage("exploreLink") var exploreLink: String = DownloadManager.exploreLink
|
@State private var gridItemLayout = [GridItem(.adaptive(minimum: MIN_SIZE))]
|
||||||
#endif
|
@State private var wallpapers: [DownloadableWallpaper] = []
|
||||||
|
|
||||||
|
@State var wallpaperTypeSelected = 0
|
||||||
|
@State var wallpaperTypeShown = DownloadableWallpaper.WallpaperType.custom
|
||||||
|
|
||||||
|
@State var filterType: FilterType = .random
|
||||||
|
@State var searchTerm: String = ""
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
NavigationView {
|
||||||
#if DEBUG
|
ScrollView {
|
||||||
let websiteURL = URL(string: exploreLink)
|
PullToRefresh(coordinateSpaceName: "pullToRefresh") {
|
||||||
#else
|
// refresh
|
||||||
let websiteURL = URL(string: DownloadManager.exploreLink)
|
wallpapers.removeAll()
|
||||||
#endif
|
Haptic.shared.play(.light)
|
||||||
if let error = error {
|
//URLCache.imageCache.removeAllCachedResponses()
|
||||||
|
loadWallpapers()
|
||||||
|
}
|
||||||
VStack {
|
VStack {
|
||||||
Text(error.localizedDescription)
|
Picker("", selection: $wallpaperTypeSelected) {
|
||||||
.foregroundStyle(.red)
|
Text("Custom").tag(0)
|
||||||
|
Text("Apple").tag(1)
|
||||||
|
}
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 24)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
|
||||||
|
if wallpapers.isEmpty {
|
||||||
|
ProgressView()
|
||||||
|
.scaleEffect(1.75)
|
||||||
|
.navigationTitle("Explore")
|
||||||
|
} else {
|
||||||
|
LazyVGrid(columns: gridItemLayout) {
|
||||||
|
ForEach(wallpapers) { wallpaper in
|
||||||
|
if searchTerm == "" || wallpaper.name.lowercased().contains(searchTerm.lowercased()) || wallpaper.authors.lowercased().contains(searchTerm.lowercased()) {
|
||||||
|
Button(action: {
|
||||||
|
DownloadManager.shared.startTendiesDownload(for: cowabungaAPI.getDownloadURLForWallpaper(wallpaper: wallpaper))
|
||||||
|
}) {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
CachedAsyncImage(url: cowabungaAPI.getPreviewURLForWallpaper(wallpaper: wallpaper), urlCache: .imageCache) { image in
|
||||||
|
image
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.cornerRadius(10, corners: .topLeft)
|
||||||
|
.cornerRadius(10, corners: .topRight)
|
||||||
|
} placeholder: {
|
||||||
|
Color.gray
|
||||||
|
.frame(height: MIN_SIZE)
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
VStack(spacing: 4) {
|
||||||
|
HStack {
|
||||||
|
Text(wallpaper.name)
|
||||||
|
.foregroundStyle(Color(uiColor: .label))
|
||||||
|
.minimumScaleFactor(0.5)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
Text(wallpaper.authors)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.font(.caption)
|
||||||
|
.minimumScaleFactor(0.5)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.lineLimit(1)
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "arrow.down.circle")
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.frame(height: 58)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(minWidth: MIN_SIZE)
|
||||||
|
.background(Color(uiColor: .secondarySystemBackground))
|
||||||
|
.cornerRadius(10)
|
||||||
|
.padding(4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.searchable(text: $searchTerm)
|
||||||
|
.coordinateSpace(name: "pullToRefresh")
|
||||||
|
.navigationTitle("Explore")
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
isLoading = true
|
showFilterChangerPopup()
|
||||||
self.error = nil
|
|
||||||
}) {
|
}) {
|
||||||
Text("Retry")
|
Image(systemName: "line.3.horizontal.decrease.circle")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let url = websiteURL {
|
}
|
||||||
WallpaperWebView(url, isLoading: $isLoading, error: $error)
|
}
|
||||||
if isLoading {
|
.onAppear {
|
||||||
ProgressView()
|
loadWallpapers()
|
||||||
}
|
}
|
||||||
} else {
|
.onChange(of: wallpaperTypeSelected) { newValue in
|
||||||
Text("Failed to load explore page.")
|
let map = [0: DownloadableWallpaper.WallpaperType.custom, 1: .apple, 2: .template]
|
||||||
|
wallpaperTypeShown = map[newValue]!
|
||||||
|
|
||||||
|
wallpapers.removeAll()
|
||||||
|
loadWallpapers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadWallpapers() {
|
||||||
|
Task {
|
||||||
|
do {
|
||||||
|
wallpapers = try await cowabungaAPI.fetchWallpapers(type: wallpaperTypeShown)
|
||||||
|
wallpapers = cowabungaAPI.filterWallpapers(wallpapers: wallpapers, filterType: filterType)
|
||||||
|
} catch {
|
||||||
|
UIApplication.shared.alert(title: NSLocalizedString("Failed to fetch wallpapers", comment: ""), body: error.localizedDescription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showFilterChangerPopup() {
|
||||||
|
// create and configure alert controller
|
||||||
|
let alert = UIAlertController(title: NSLocalizedString("Filter Wallpapers", comment: ""), message: "", preferredStyle: .actionSheet)
|
||||||
|
|
||||||
|
// create the actions
|
||||||
|
for type in FilterType.allCases {
|
||||||
|
let newAction = UIAlertAction(title: type.rawValue, style: .default) { (action) in
|
||||||
|
// apply the filter type
|
||||||
|
filterType = type
|
||||||
|
wallpapers.removeAll()
|
||||||
|
loadWallpapers()
|
||||||
|
}
|
||||||
|
if filterType == type {
|
||||||
|
// add a check mark
|
||||||
|
newAction.setValue(true, forKey: "checked")
|
||||||
|
}
|
||||||
|
alert.addAction(newAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { (action) in
|
||||||
|
// cancels the action
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the actions
|
||||||
|
alert.addAction(cancelAction)
|
||||||
|
|
||||||
|
let view: UIView = UIApplication.shared.windows.first!.rootViewController!.view
|
||||||
|
// present popover for iPads
|
||||||
|
alert.popoverPresentationController?.sourceView = view // prevents crashing on iPads
|
||||||
|
alert.popoverPresentationController?.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.maxY, width: 0, height: 0) // show up at center bottom on iPads
|
||||||
|
|
||||||
|
// present the alert
|
||||||
|
UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PullToRefresh: View {
|
||||||
|
var coordinateSpaceName: String
|
||||||
|
var onRefresh: ()->Void
|
||||||
|
|
||||||
|
@State var needRefresh: Bool = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { geo in
|
||||||
|
if (geo.frame(in: .named(coordinateSpaceName)).midY > 50) {
|
||||||
|
Spacer()
|
||||||
|
.onAppear {
|
||||||
|
needRefresh = true
|
||||||
|
}
|
||||||
|
} else if (geo.frame(in: .named(coordinateSpaceName)).maxY < 10) {
|
||||||
|
Spacer()
|
||||||
|
.onAppear {
|
||||||
|
if needRefresh {
|
||||||
|
needRefresh = false
|
||||||
|
onRefresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
if needRefresh {
|
||||||
|
ProgressView()
|
||||||
|
.scaleEffect(1.75)
|
||||||
|
.onAppear {
|
||||||
|
Haptic.shared.play(.light)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text("")
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}.padding(.top, -50)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,11 +13,6 @@ struct SettingsView: View {
|
|||||||
@AppStorage("cpHash") var cpHash: String = "" // CarPlay hash
|
@AppStorage("cpHash") var cpHash: String = "" // CarPlay hash
|
||||||
@AppStorage("ignoreDurationLimit") var ignoreDurationLimit: Bool = false
|
@AppStorage("ignoreDurationLimit") var ignoreDurationLimit: Bool = false
|
||||||
|
|
||||||
// DEBUG
|
|
||||||
#if DEBUG
|
|
||||||
@AppStorage("exploreLink") var exploreLink: String = DownloadManager.exploreLink
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@State var checkingForHash: Bool = false
|
@State var checkingForHash: Bool = false
|
||||||
@State var hashCheckTask: Task<Void, any Error>? = nil
|
@State var hashCheckTask: Task<Void, any Error>? = nil
|
||||||
|
|
||||||
@@ -58,19 +53,6 @@ struct SettingsView: View {
|
|||||||
Label("App Hash", systemImage: "lock.app.dashed")
|
Label("App Hash", systemImage: "lock.app.dashed")
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
Section {
|
|
||||||
VStack {
|
|
||||||
Text(String("Explore Page URL"))
|
|
||||||
TextField(String("Enter Explore Page URL"), text: $exploreLink)
|
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
||||||
.font(.system(.body, design: .monospaced))
|
|
||||||
}
|
|
||||||
} header: {
|
|
||||||
Label(String("Debug"), systemImage: "ant")
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Toggle(isOn: $ignoreDurationLimit, label: {
|
Toggle(isOn: $ignoreDurationLimit, label: {
|
||||||
Label("Disable Video Duration Limit", systemImage: "ruler")
|
Label("Disable Video Duration Limit", systemImage: "ruler")
|
||||||
|
|||||||
Reference in new issue
Block a user