custom explore page + api
This commit is contained in:
1 parent
5a94ea73d9
commit
4d6b093ffd
10 files changed
+415
-49
No files matched your search
@@ -6,41 +6,209 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CachedAsyncImage
|
||||
|
||||
let MIN_SIZE: CGFloat = 165
|
||||
|
||||
struct ExploreView: View {
|
||||
@State private var isLoading = true
|
||||
@State private var error: Error? = nil
|
||||
@ObservedObject var cowabungaAPI = CowabungaAPI.shared
|
||||
|
||||
#if DEBUG
|
||||
@AppStorage("exploreLink") var exploreLink: String = DownloadManager.exploreLink
|
||||
#endif
|
||||
// lazyvgrid
|
||||
@State private var gridItemLayout = [GridItem(.adaptive(minimum: MIN_SIZE))]
|
||||
@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 {
|
||||
ZStack {
|
||||
#if DEBUG
|
||||
let websiteURL = URL(string: exploreLink)
|
||||
#else
|
||||
let websiteURL = URL(string: DownloadManager.exploreLink)
|
||||
#endif
|
||||
if let error = error {
|
||||
NavigationView {
|
||||
ScrollView {
|
||||
PullToRefresh(coordinateSpaceName: "pullToRefresh") {
|
||||
// refresh
|
||||
wallpapers.removeAll()
|
||||
Haptic.shared.play(.light)
|
||||
//URLCache.imageCache.removeAllCachedResponses()
|
||||
loadWallpapers()
|
||||
}
|
||||
VStack {
|
||||
Text(error.localizedDescription)
|
||||
.foregroundStyle(.red)
|
||||
Picker("", selection: $wallpaperTypeSelected) {
|
||||
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: {
|
||||
isLoading = true
|
||||
self.error = nil
|
||||
showFilterChangerPopup()
|
||||
}) {
|
||||
Text("Retry")
|
||||
Image(systemName: "line.3.horizontal.decrease.circle")
|
||||
}
|
||||
}
|
||||
} else if let url = websiteURL {
|
||||
WallpaperWebView(url, isLoading: $isLoading, error: $error)
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
}
|
||||
} else {
|
||||
Text("Failed to load explore page.")
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
loadWallpapers()
|
||||
}
|
||||
.onChange(of: wallpaperTypeSelected) { newValue in
|
||||
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("ignoreDurationLimit") var ignoreDurationLimit: Bool = false
|
||||
|
||||
// DEBUG
|
||||
#if DEBUG
|
||||
@AppStorage("exploreLink") var exploreLink: String = DownloadManager.exploreLink
|
||||
#endif
|
||||
|
||||
@State var checkingForHash: Bool = false
|
||||
@State var hashCheckTask: Task<Void, any Error>? = nil
|
||||
|
||||
@@ -58,19 +53,6 @@ struct SettingsView: View {
|
||||
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 {
|
||||
Toggle(isOn: $ignoreDurationLimit, label: {
|
||||
Label("Disable Video Duration Limit", systemImage: "ruler")
|
||||
|
||||
Reference in new issue
Block a user