From 864e03cd4a913374021b9dec9f37a92a78939e2d Mon Sep 17 00:00:00 2001 From: ny Date: Mon, 24 Jun 2024 01:51:28 -0400 Subject: [PATCH] Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent --- AltStore.xcodeproj/project.pbxproj | 8 +- AltStore/Intents/IntentHandler.swift | 20 +- AltStore/Managing Apps/AppManager.swift | 18 +- .../FetchAnisetteDataOperation.swift | 3 +- AltStore/Operations/VerifyAppOperation.swift | 6 +- AltStore/Settings.bundle/Root.plist | 111 ----------- .../Settings.bundle/en.lproj/Root.strings | Bin 546 -> 0 bytes AltStore/Settings/AnisetteManager.swift | 5 + AltStore/Settings/AnisetteServerList.swift | 183 ++++++++++++++++++ AltStore/Settings/Settings.storyboard | 43 +--- .../Settings/SettingsViewController.swift | 63 +++--- .../Extensions/UserDefaults+AltStore.swift | 7 +- AltStoreCore/Model/InstalledApp.swift | 2 - 13 files changed, 274 insertions(+), 195 deletions(-) delete mode 100644 AltStore/Settings.bundle/Root.plist delete mode 100644 AltStore/Settings.bundle/en.lproj/Root.strings create mode 100644 AltStore/Settings/AnisetteServerList.swift diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 314bd875..4308528c 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 0EA1667B2ADFE140003015C1 /* Structure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1665A2ADFE0D2003015C1 /* Structure.cpp */; }; 0EA1667D2ADFE140003015C1 /* xplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166592ADFE0D2003015C1 /* xplist.c */; }; 0EA1667E2ADFE140003015C1 /* time64.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1664C2ADFE0D1003015C1 /* time64.c */; }; + 0EA4263A2C2230150026D7FB /* AnisetteServerList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA426392C2230150026D7FB /* AnisetteServerList.swift */; }; 0EA4B9BC2AE4A414009209CE /* plist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA4B9BB2AE4A3F6009209CE /* plist.c */; }; 0EE7FDC42BE8BC7900D1E390 /* ALTLocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */; }; 0EE7FDC62BE8CEA300D1E390 /* ALTLocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */; }; @@ -50,7 +51,6 @@ 19104DBC2909C4E500C49C7B /* libEmotionalDamage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19104DB22909C06C00C49C7B /* libEmotionalDamage.a */; }; 191E5FB4290A5DA0001A3B7C /* libminimuxer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 191E5FAB290A5D92001A3B7C /* libminimuxer.a */; }; 191E5FDC290AFA5C001A3B7C /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 191E5FDB290AFA5C001A3B7C /* OpenSSL */; }; - 1920B04F2924AC8300744F60 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 1920B04E2924AC8300744F60 /* Settings.bundle */; }; 19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */; }; 4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; }; 4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; }; @@ -543,6 +543,7 @@ 0EA166652ADFE122003015C1 /* hashtable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = hashtable.h; path = Dependencies/libplist/src/hashtable.h; sourceTree = SOURCE_ROOT; }; 0EA166662ADFE122003015C1 /* base64.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = base64.h; path = Dependencies/libplist/src/base64.h; sourceTree = SOURCE_ROOT; }; 0EA166672ADFE122003015C1 /* strbuf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = strbuf.h; path = Dependencies/libplist/src/strbuf.h; sourceTree = SOURCE_ROOT; }; + 0EA426392C2230150026D7FB /* AnisetteServerList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnisetteServerList.swift; sourceTree = ""; }; 0EA4B9BB2AE4A3F6009209CE /* plist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = plist.c; path = Dependencies/libplist/src/plist.c; sourceTree = SOURCE_ROOT; }; 0EE7FDC02BE8BC2100D1E390 /* ALTWrappedError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTWrappedError.m; sourceTree = ""; }; 0EE7FDC22BE8BC4200D1E390 /* ALTWrappedError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTWrappedError.h; sourceTree = ""; }; @@ -551,7 +552,6 @@ 19104DB22909C06C00C49C7B /* libEmotionalDamage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libEmotionalDamage.a; sourceTree = BUILT_PRODUCTS_DIR; }; 19104DB42909C06D00C49C7B /* EmotionalDamage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmotionalDamage.swift; sourceTree = ""; }; 191E5FAB290A5D92001A3B7C /* libminimuxer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libminimuxer.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 1920B04E2924AC8300744F60 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; 19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTeamViewController.swift; sourceTree = ""; }; 9961EC2D29BE9F2E00AF2C6F /* minimuxer-helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "minimuxer-helpers.swift"; path = "Dependencies/minimuxer/minimuxer-helpers.swift"; sourceTree = SOURCE_ROOT; }; 99F87D1629D8E4C900B40039 /* SwiftBridgeCore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftBridgeCore.swift; path = Dependencies/minimuxer/SwiftBridgeCore.swift; sourceTree = SOURCE_ROOT; }; @@ -1601,7 +1601,6 @@ BFD247962284D7C100981D42 /* Resources */, BF6C8FA8242935CA00125131 /* Dependencies */, BFD247972284D7D800981D42 /* Supporting Files */, - 1920B04E2924AC8300744F60 /* Settings.bundle */, ); path = AltStore; sourceTree = ""; @@ -1685,6 +1684,7 @@ children = ( BFE60737231ADF49002B0E8E /* Settings.storyboard */, BFE60739231ADF82002B0E8E /* SettingsViewController.swift */, + 0EA426392C2230150026D7FB /* AnisetteServerList.swift */, BFE6073F231AFD2A002B0E8E /* InsetGroupTableViewCell.swift */, BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */, BFE6073B231AE1E7002B0E8E /* SettingsHeaderFooterView.xib */, @@ -2238,7 +2238,6 @@ BFD2477A2284B9A700981D42 /* LaunchScreen.storyboard in Resources */, BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */, BFD247772284B9A700981D42 /* Assets.xcassets in Resources */, - 1920B04F2924AC8300744F60 /* Settings.bundle in Resources */, BFF0B6922321A305007A79E1 /* AboutPatreonHeaderView.xib in Resources */, BFB6B22423187A3D0022A802 /* NewsCollectionViewCell.xib in Resources */, BFD247752284B9A500981D42 /* Main.storyboard in Resources */, @@ -2540,6 +2539,7 @@ BF41B808233433C100C593A3 /* LoadingState.swift in Sources */, BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */, D5ACE84528E3B8450021CAB9 /* ClearAppCacheOperation.swift in Sources */, + 0EA4263A2C2230150026D7FB /* AnisetteServerList.swift in Sources */, D5F2F6A92720B7C20081CCF5 /* PatchViewController.swift in Sources */, B39F16132918D7C5002E9404 /* Consts.swift in Sources */, BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */, diff --git a/AltStore/Intents/IntentHandler.swift b/AltStore/Intents/IntentHandler.swift index ff8db924..641b00b3 100644 --- a/AltStore/Intents/IntentHandler.swift +++ b/AltStore/Intents/IntentHandler.swift @@ -8,6 +8,7 @@ import Foundation +import minimuxer import AltStoreCore @available(iOS 14, *) @@ -39,8 +40,12 @@ final class IntentHandler: NSObject, RefreshAllIntentHandling // Give ourselves 9 extra seconds before starting handle() timeout timer. // 10 seconds or longer results in timeout regardless. - self.queue.asyncAfter(deadline: .now() + 9.0) { - self.finish(intent, response: RefreshAllIntentResponse(code: .ready, userActivity: nil)) + self.queue.asyncAfter(deadline: .now() + 8.0) { + if minimuxer.ready() { + self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil)) + } else { + self.finish(intent, response: RefreshAllIntentResponse(code: .failure, userActivity: nil)) + } } if !DatabaseManager.shared.isStarted @@ -52,12 +57,14 @@ final class IntentHandler: NSObject, RefreshAllIntentHandling } else { + self.finish(intent, response: RefreshAllIntentResponse(code: .ready, userActivity: nil)) self.refreshApps(intent: intent) } } } else { + self.finish(intent, response: RefreshAllIntentResponse(code: .ready, userActivity: nil)) self.refreshApps(intent: intent) } } @@ -83,6 +90,11 @@ final class IntentHandler: NSObject, RefreshAllIntentHandling // We took too long to finish and return the final result, // so we'll now present a normal notification when finished. operation.presentsFinishedNotification = true + if minimuxer.ready() { + self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil)) + } else { + self.finish(intent, response: RefreshAllIntentResponse(code: .failure, userActivity: nil)) + } } self.finish(intent, response: RefreshAllIntentResponse(code: .inProgress, userActivity: nil)) @@ -106,6 +118,8 @@ private extension IntentHandler { // Queue response in case refreshing finishes after confirm() but before handle(). self.queuedResponses[intent] = response + + UIApplication.shared.perform(#selector(NSXPCConnection.suspend)) } } } @@ -126,10 +140,12 @@ private extension IntentHandler } self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil)) + UIApplication.shared.perform(#selector(NSXPCConnection.suspend)) } catch ~RefreshErrorCode.noInstalledApps { self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil)) + UIApplication.shared.perform(#selector(NSXPCConnection.suspend)) } catch let error as NSError { diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index 5098822a..46480934 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -14,6 +14,7 @@ import Intents import Combine import WidgetKit +import minimuxer import AltStoreCore import AltSign import Roxas @@ -1105,7 +1106,9 @@ private extension AppManager switch result { case .failure(let error): context.error = error - case .success(let provisioningProfiles): context.provisioningProfiles = provisioningProfiles + case .success(let provisioningProfiles): + context.provisioningProfiles = provisioningProfiles + print("PROVISIONING PROFILES \(context.provisioningProfiles)") } } fetchProvisioningProfilesOperation.addDependency(refreshAnisetteDataOperation) @@ -1312,14 +1315,21 @@ private extension AppManager case .success(let installedApp): completionHandler(.success(installedApp)) + case .failure(MinimuxerError.ProfileInstall): + completionHandler(.failure(OperationError.noWiFi)) + case .failure(ALTServerError.unknownRequest), .failure(OperationError.appNotFound(name: app.name)): // Fall back to installation if AltServer doesn't support newer provisioning profile requests, // OR if the cached app could not be found and we may need to redownload it. app.managedObjectContext?.performAndWait { // Must performAndWait to ensure we add operations before we return. - let installProgress = self._install(app, operation: operation, group: group) { (result) in - completionHandler(result) + if minimuxer.ready() { + let installProgress = self._install(app, operation: operation, group: group) { (result) in + completionHandler(result) + } + progress.addChild(installProgress, withPendingUnitCount: 40) + } else { + completionHandler(.failure(OperationError.noWiFi)) } - progress.addChild(installProgress, withPendingUnitCount: 40) } case .failure(let error): diff --git a/AltStore/Operations/FetchAnisetteDataOperation.swift b/AltStore/Operations/FetchAnisetteDataOperation.swift index 8aac0eaa..c2932224 100644 --- a/AltStore/Operations/FetchAnisetteDataOperation.swift +++ b/AltStore/Operations/FetchAnisetteDataOperation.swift @@ -45,7 +45,7 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc return } - self.url = AnisetteManager.currentURL + self.url = URL(string: UserDefaults.standard.menuAnisetteURL) print("Anisette URL: \(self.url!.absoluteString)") if let identifier = Keychain.shared.identifier, @@ -408,6 +408,7 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc func fetchAnisetteV3(_ identifier: String, _ adiPb: String) { fetchClientInfo { print("Fetching anisette V3") + let url = UserDefaults.standard.menuAnisetteURL var request = URLRequest(url: self.url!.appendingPathComponent("v3").appendingPathComponent("get_headers")) request.httpMethod = "POST" request.httpBody = try! JSONSerialization.data(withJSONObject: [ diff --git a/AltStore/Operations/VerifyAppOperation.swift b/AltStore/Operations/VerifyAppOperation.swift index 00c4ccf4..5fc94972 100644 --- a/AltStore/Operations/VerifyAppOperation.swift +++ b/AltStore/Operations/VerifyAppOperation.swift @@ -119,8 +119,10 @@ final class VerifyAppOperation: ResultOperation guard let app = self.context.app else { throw OperationError.invalidParameters } - guard app.bundleIdentifier == self.context.bundleIdentifier else { - throw VerificationError.mismatchedBundleIdentifiers(sourceBundleID: self.context.bundleIdentifier, app: app) + if !["ny.litritt.ignited", "com.litritt.ignited"].contains(where: { $0 == app.bundleIdentifier }) { + guard app.bundleIdentifier == self.context.bundleIdentifier else { + throw VerificationError.mismatchedBundleIdentifiers(sourceBundleID: self.context.bundleIdentifier, app: app) + } } guard ProcessInfo.processInfo.isOperatingSystemAtLeast(app.minimumiOSVersion) else { diff --git a/AltStore/Settings.bundle/Root.plist b/AltStore/Settings.bundle/Root.plist deleted file mode 100644 index 026148e0..00000000 --- a/AltStore/Settings.bundle/Root.plist +++ /dev/null @@ -1,111 +0,0 @@ - - - - - StringsTable - Root - ApplicationGroupContainerIdentifier - group.$(APP_GROUP_IDENTIFIER) - PreferenceSpecifiers - - - Type - PSMultiValueSpecifier - Title - Anisette Server - Key - customAnisetteURL - DefaultValue - https://ani.sidestore.io - Titles - - SideStore - SideStore (.zip) - SideStore (.xyz) - Macley (US) - Jawshoeadan - WesleyBryie - Stossy11 - - Values - - https://ani.sidestore.io - https://ani.sidestore.zip - https://ani.846969.xyz - http://5.249.163.88:6969/ - https://anisette.jawshoeadan.me - https://ani.wesbryie.com - https://anisette.stossy11.com - - - - Type - PSGroupSpecifier - Title - Danger Zone - FooterText - If you disable the toggle then app will use the server you input into the "Anisette URL" box rather than one selected from the above selector. - - - Type - PSToggleSwitchSpecifier - Title - Use preferred servers - Key - textServer - DefaultValue - - FooterText - chicken - - - Type - PSTextFieldSpecifier - Title - Anisette URL - Key - textInputAnisetteURL - AutocapitalizationType - None - AutocorrectionType - No - KeyboardType - URL - - - Type - PSGroupSpecifier - Title - SideJITServer - FooterText - If you disable the toggle then the app will not be able to access and use SideJITServer on iOS 17+. "SideJITServer IP" is not needed but is recommended for stability. - - - Type - PSToggleSwitchSpecifier - Title - Enable SideJIT Support - Key - sidejitenable - DefaultValue - - FooterText - chicken - - - Type - PSTextFieldSpecifier - Title - SideJITServer IP - Key - textInputSideJITServerurl - AutocapitalizationType - None - AutocorrectionType - No - KeyboardType - URL - - - - diff --git a/AltStore/Settings.bundle/en.lproj/Root.strings b/AltStore/Settings.bundle/en.lproj/Root.strings deleted file mode 100644 index 8cd87b9d6b20c1fbf87bd4db3db267fca5ad4df9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 546 zcmaixOHRW;5JYRuDMndFh#Ua1V1d}N;sVAV2TO?uC3a9aJn*VxFrY}tnon0(S66#J z-d9>G>6W!ur(SDqlp`9nn~*(m%iWnv?yq`Qfp6XbK1?+om~~#r)ZnhkYQU_VbfjuT zHNn`CX<0sd*m1A}>&5sU$akD=GTXJ1e diff --git a/AltStore/Settings/AnisetteManager.swift b/AltStore/Settings/AnisetteManager.swift index 4edc4617..fa269825 100644 --- a/AltStore/Settings/AnisetteManager.swift +++ b/AltStore/Settings/AnisetteManager.swift @@ -10,6 +10,11 @@ import Foundation public struct AnisetteManager { + var menuURL: String { + var url: String + url = UserDefaults.standard.menuAnisetteURL + return url + } /// User defined URL from Settings/UserDefaults static var userURL: String? { var urlString: String? diff --git a/AltStore/Settings/AnisetteServerList.swift b/AltStore/Settings/AnisetteServerList.swift new file mode 100644 index 00000000..8cf3e081 --- /dev/null +++ b/AltStore/Settings/AnisetteServerList.swift @@ -0,0 +1,183 @@ +// +// AnisetteServerList.swift +// SideStore +// +// Created by ny on 6/18/24. +// Copyright © 2024 SideStore. All rights reserved. +// + +import UIKit +import SwiftUI +import AltStoreCore + +typealias SUIButton = SwiftUI.Button + +// MARK: - AnisetteServerData +struct AnisetteServerData: Codable { + let servers: [Server] +} + +// MARK: - Server +struct Server: Codable { + var name: String + var address: String +} + +struct AniServer: Codable { + var name: String + var url: URL +} + +class AnisetteViewModel: ObservableObject { + @Published var selected: String = "" + + @Published var source: String = "https://servers.sidestore.io/servers.json" + @Published var servers: [Server] = [] + + func getListOfServers() { + URLSession.shared.dataTask(with: URL(string: source)!) { data, response, error in + if let error = error { + return + } + if let data = data { + do { + let servers = try Foundation.JSONDecoder().decode(AnisetteServerData.self, from: data) + DispatchQueue.main.async { + self.servers = servers.servers.map { Server(name: $0.name, address: $0.address) } + } + } catch { + + } + } + } + .resume() + for server in servers { + print(server) + print(server.name.count) + print(server.name) + } + } + +} + +struct AnisetteServers: View { + @Environment(\.presentationMode) var presentationMode + @StateObject var viewModel: AnisetteViewModel = AnisetteViewModel() + @State var selected: String? = nil + var errorCallback: () -> () + + var body: some View { + NavigationView { + ZStack { + Color(UIColor(named: "SettingsBackground")!).ignoresSafeArea(.all) + .onAppear { + viewModel.getListOfServers() + } + VStack { + if #available(iOS 16.0, *) { + SwiftUI.List($viewModel.servers, id: \.address, selection: $selected) { server in + HStack { + VStack(alignment: .leading) { + Text("\(server.name.wrappedValue)") + .font(.headline) + .underline(true, color: .white) + Text("\(server.address.wrappedValue)") + .fontWeight(.thin) + } + if selected != nil { + if server.address.wrappedValue == selected { + Spacer() + Image(systemName: "checkmark") + .onAppear { + UserDefaults.standard.menuAnisetteURL = server.address.wrappedValue + print(UserDefaults.synchronize(.standard)()) + print(UserDefaults.standard.menuAnisetteURL) + print(server.address.wrappedValue) + } + } + } + } + .backgroundStyle((selected == nil) ? Color(UIColor(named: "SettingsHighlighted")!) : Color(UIColor(named: "SettingsBackground")!)) + .listRowSeparatorTint(.white) + .listRowBackground((selected == nil) ? Color(UIColor(named: "SettingsHighlighted")!).ignoresSafeArea(.all) : Color(UIColor(named: "SettingsBackground")!).ignoresSafeArea(.all)) + } + .listStyle(.plain) + .scrollContentBackground(.hidden) + .listRowBackground(Color(UIColor(named: "SettingsBackground")!).ignoresSafeArea(.all)) + + } else { + List(selection: $selected) { + ForEach($viewModel.servers, id: \.name) { server in + VStack { + HStack { + Text("\(server.name.wrappedValue)") + .foregroundColor(.white) + .frame(alignment: .center) + Text("\(server.address.wrappedValue)") + .foregroundColor(.white) + .frame(alignment: .center) + } + } + Spacer() + } + } + .listStyle(.plain) + // Fallback on earlier versions + } + if #available(iOS 15.0, *) { + TextField("Anisette Server List", text: $viewModel.source) + .padding(.leading, 5) + .padding(.vertical, 10) + .frame(alignment: .center) + .textFieldStyle(.plain) + .border(.white, width: 1) + .onSubmit { + UserDefaults.standard.menuAnisetteList = viewModel.source + viewModel.getListOfServers() + } + SUIButton(action: { + viewModel.getListOfServers() + }, label: { + Text("Refresh Servers") + }) + .padding(.bottom, 20) + SUIButton(role: .destructive, action: { +#if !DEBUG + if Keychain.shared.adiPb != nil { + Keychain.shared.adiPb = nil + } +#endif + print("Cleared adi.pb from keychain") + errorCallback() + self.presentationMode.wrappedValue.dismiss() + }, label: { + Text("Reset adi.pb") +// if (selected != nil) { +// Text("\(selected!.uuidString)") +// } + }) + .padding(.bottom, 20) + } else { + // Fallback on earlier versions + } + } + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .navigationTitle("Anisette Servers") + .onAppear { + if UserDefaults.standard.menuAnisetteList != "" { + viewModel.source = UserDefaults.standard.menuAnisetteList + } else { + viewModel.source = "https://servers.sidestore.io/servers.json" + } + print(UserDefaults.standard.menuAnisetteURL) + print(UserDefaults.standard.menuAnisetteList) + } + } +} + + +#Preview { + AnisetteServers(selected: "", errorCallback: {}) +} diff --git a/AltStore/Settings/Settings.storyboard b/AltStore/Settings/Settings.storyboard index 6010a907..db0db89b 100644 --- a/AltStore/Settings/Settings.storyboard +++ b/AltStore/Settings/Settings.storyboard @@ -3,7 +3,7 @@ - + @@ -20,8 +20,8 @@ -