2024-08-06 10:43:52 +09:00
//
// A n i s e t t e S e r v e r L i s t . s w i f t
// S i d e S t o r e
//
// C r e a t e d b y n y o n 6 / 1 8 / 2 4 .
// C o p y r i g h t © 2 0 2 4 S i d e S t o r e . A l l r i g h t s r e s e r v e d .
//
import UIKit
import SwiftUI
import AltStoreCore
typealias SUIButton = SwiftUI . Button
// MARK: - A n i s e t t e S e r v e r D a t a
struct AnisetteServerData : Codable {
let servers : [ Server ]
}
// MARK: - S e r v e r
struct Server : Codable {
var name : String
var address : String
}
class AnisetteViewModel : ObservableObject {
@ Published var selected : String = " "
@ Published var source : String = " https://servers.sidestore.io/servers.json "
@ Published var servers : [ Server ] = [ ]
2024-10-23 08:16:37 +08:00
init ( ) {
// u s i n g t h e c u s t o m A n i s e t t e l i s t
if ! UserDefaults . standard . menuAnisetteList . isEmpty {
self . source = UserDefaults . standard . menuAnisetteList
}
}
2024-12-15 23:00:12 +05:30
@ MainActor
func getCurrentListOfServers ( _ completionHandler : @ escaping ( Result < Void , Error > ) -> Void = { _ in } ) {
// d i s p a t c h f e t c h o p e r a t i o n b u t d o n ' t d o a b l o c k i n g w a i t f o r r e s u l t s
Task {
do {
let anisetteServers = try await AnisetteViewModel . getListOfServers ( serverSource : self . source )
// U p d a t e U I - r e l a t e d s t a t e o n t h e m a i n t h r e a d
2024-12-14 07:54:11 +05:30
self . servers = anisetteServers
2024-12-15 23:00:12 +05:30
print ( " AnisetteViewModel: Server list refresh request completed for sourceURL: \( self . source ) " )
completionHandler ( . success ( ( ) ) )
} catch {
print ( " AnisetteViewModel: Server list refresh request Failed for sourceURL: \( self . source ) Error: \( error ) " )
completionHandler ( . failure ( error ) )
2024-12-14 07:54:11 +05:30
}
2024-12-14 06:51:08 +05:30
}
}
2024-12-15 23:00:12 +05:30
static func getListOfServers ( serverSource : String ) async throws -> [ Server ] {
2024-12-14 06:51:08 +05:30
var aniServers : [ Server ] = [ ]
guard let url = URL ( string : serverSource ) else {
return aniServers
}
2024-12-15 23:00:12 +05:30
2024-11-28 19:08:56 +05:30
// D O N O T u s e l o c a l c a c h e w h e n f e t c h i n g a n i s e t t e s e r v e r s
var request = URLRequest ( url : url )
request . cachePolicy = . reloadIgnoringLocalCacheData
2024-12-14 06:51:08 +05:30
2024-12-15 23:00:12 +05:30
do {
// U s e a s y n c / a w a i t p a t t e r n h e r e , a v o i d i n g C h e c k e d C o n t i n u a t i o n d i r e c t l y
let ( data , response ) = try await URLSession . shared . data ( for : request )
// C h e c k i f t h e r e s p o n s e i s v a l i d a n d h a s a 2 x x H T T P s t a t u s c o d e
guard let httpResponse = response as ? HTTPURLResponse , ( 200. . . 299 ) . contains ( httpResponse . statusCode ) else {
// H a n d l e n o n - 2 x x s t a t u s c o d e s
let statusCode = ( response as ? HTTPURLResponse ) ? . statusCode ? ? - 1
throw NSError ( domain : " AnisetteViewModel: ServerError " , code : statusCode , userInfo : [ NSLocalizedDescriptionKey : " Request failed with status code: \( statusCode ) " ] )
}
let decoder = Foundation . JSONDecoder ( )
let servers = try decoder . decode ( AnisetteServerData . self , from : data )
print ( " AnisetteViewModel: JSON Decode successful for sourceURL: \( serverSource ) servers: \( servers ) " )
aniServers . append ( contentsOf : servers . servers )
// S t o r e s e r v e r a d d r e s s e s a s l i s t
UserDefaults . standard . menuAnisetteServersList = aniServers . map ( \ . address )
return aniServers
} catch {
if let urlError = error as ? URLError {
print ( " AnisetteViewModel: URL Error: \( urlError . localizedDescription ) " )
} else if let decodingError = error as ? DecodingError {
print ( " AnisetteViewModel: Failed to decode JSON: \( decodingError . localizedDescription ) " )
} else {
print ( " AnisetteViewModel: An unexpected error occurred: \( error . localizedDescription ) " )
}
throw error // P r o p a g a t e t h e e r r o r
2024-12-14 06:51:08 +05:30
}
2024-08-06 10:43:52 +09:00
}
}
2024-12-15 23:00:12 +05:30
struct AnisetteServersView : View {
2024-08-06 10:43:52 +09:00
@ Environment ( \ . presentationMode ) var presentationMode
@ StateObject var viewModel : AnisetteViewModel = AnisetteViewModel ( )
@ State var selected : String ? = nil
2024-10-23 08:16:37 +08:00
@ State private var showingConfirmation = false
2024-08-06 10:43:52 +09:00
var errorCallback : ( ) -> ( )
2024-12-15 23:00:12 +05:30
var refreshCallback : ( Result < Void , any Error > ) -> Void
2024-08-06 10:43:52 +09:00
var body : some View {
2024-08-12 15:14:57 -04:00
ZStack {
2024-08-12 16:53:45 -04:00
Color ( UIColor . systemBackground )
. ignoresSafeArea ( )
2024-08-12 15:14:57 -04:00
. onAppear {
2024-12-15 23:00:12 +05:30
viewModel . getCurrentListOfServers ( refreshCallback )
2024-08-12 15:14:57 -04:00
}
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 )
. foregroundColor ( . primary )
Text ( " \( server . address . wrappedValue ) " )
. font ( . subheadline )
. foregroundColor ( . secondary )
}
Spacer ( )
if selected != nil {
if server . address . wrappedValue = = selected {
Spacer ( )
Image ( systemName : " checkmark.circle.fill " )
. foregroundColor ( . accentColor )
. onAppear {
UserDefaults . standard . menuAnisetteURL = server . address . wrappedValue
print ( UserDefaults . synchronize ( . standard ) ( ) )
print ( UserDefaults . standard . menuAnisetteURL )
print ( server . address . wrappedValue )
2024-08-06 10:43:52 +09:00
}
}
2024-08-12 15:14:57 -04:00
}
2024-08-06 10:43:52 +09:00
}
2024-08-12 15:14:57 -04:00
. padding ( )
. background ( RoundedRectangle ( cornerRadius : 10 ) . fill ( Color ( UIColor . secondarySystemBackground ) ) )
2024-08-12 16:53:45 -04:00
. shadow ( color : Color . black . opacity ( 0.2 ) , radius : 5 , x : 0 , y : 5 )
2024-08-12 15:14:57 -04:00
}
. listStyle ( . plain )
. scrollContentBackground ( . hidden )
2024-08-12 16:53:45 -04:00
. listRowBackground ( Color ( UIColor . systemBackground ) )
2024-08-12 15:14:57 -04:00
} else {
List ( selection : $ selected ) {
ForEach ( $ viewModel . servers , id : \ . name ) { server in
VStack {
HStack {
Text ( " \( server . name . wrappedValue ) " )
. foregroundColor ( . primary )
. frame ( alignment : . center )
Text ( " \( server . address . wrappedValue ) " )
. foregroundColor ( . secondary )
. frame ( alignment : . center )
2024-08-06 10:43:52 +09:00
}
}
2024-08-12 15:14:57 -04:00
Spacer ( )
2024-08-06 10:43:52 +09:00
}
2024-08-12 15:14:57 -04:00
. padding ( )
. background ( RoundedRectangle ( cornerRadius : 10 ) . fill ( Color ( UIColor . secondarySystemBackground ) ) )
2024-08-12 16:53:45 -04:00
. shadow ( color : Color . black . opacity ( 0.2 ) , radius : 5 , x : 0 , y : 5 )
2024-08-06 10:43:52 +09:00
}
2024-08-12 15:14:57 -04:00
. listStyle ( . plain )
}
VStack ( spacing : 16 ) {
TextField ( " Anisette Server List " , text : $ viewModel . source )
. padding ( )
2024-08-12 16:53:45 -04:00
. background ( RoundedRectangle ( cornerRadius : 10 ) . fill ( Color ( UIColor . secondarySystemFill ) ) )
. foregroundColor ( . primary )
2024-08-12 15:14:57 -04:00
. frame ( height : 60 )
2024-08-12 16:53:45 -04:00
. shadow ( color : Color . black . opacity ( 0.2 ) , radius : 5 , x : 0 , y : 5 )
2024-08-12 15:14:57 -04:00
. onChange ( of : viewModel . source ) { newValue in
UserDefaults . standard . menuAnisetteList = newValue
2024-12-15 23:00:12 +05:30
// v i e w M o d e l . g e t C u r r e n t L i s t O f S e r v e r s ( r e f r e s h C a l l b a c k ) / / d o n ' t s p a m
viewModel . getCurrentListOfServers ( )
2024-08-12 15:14:57 -04:00
}
HStack ( spacing : 16 ) {
SUIButton ( action : {
presentationMode . wrappedValue . dismiss ( )
} ) {
2024-10-23 08:16:37 +08:00
HStack {
Spacer ( )
Text ( " Back " )
. fontWeight ( . semibold )
Spacer ( )
}
. contentShape ( Rectangle ( ) )
2024-08-12 15:14:57 -04:00
}
. buttonStyle ( PlainButtonStyle ( ) )
. padding ( )
. background ( RoundedRectangle ( cornerRadius : 10 ) . fill ( Color . accentColor ) )
. foregroundColor ( . white )
. shadow ( color : Color . accentColor . opacity ( 0.4 ) , radius : 10 , x : 0 , y : 5 )
2024-08-06 10:43:52 +09:00
SUIButton ( action : {
2024-12-15 23:00:12 +05:30
viewModel . getCurrentListOfServers ( refreshCallback )
2024-08-12 15:14:57 -04:00
} ) {
2024-10-23 08:16:37 +08:00
HStack {
Text ( " Refresh Servers " )
. fontWeight ( . semibold )
2024-12-16 20:23:21 +05:30
. frame ( maxWidth : . infinity , alignment : . center )
2024-10-23 08:16:37 +08:00
}
. contentShape ( Rectangle ( ) )
2024-08-12 15:14:57 -04:00
}
. buttonStyle ( PlainButtonStyle ( ) )
. padding ( )
. background ( RoundedRectangle ( cornerRadius : 10 ) . fill ( Color . accentColor ) )
. foregroundColor ( . white )
. shadow ( color : Color . accentColor . opacity ( 0.4 ) , radius : 10 , x : 0 , y : 5 )
2024-10-23 08:16:37 +08:00
2024-08-12 15:14:57 -04:00
}
SUIButton ( action : {
2024-10-23 08:16:37 +08:00
showingConfirmation = true
2024-08-12 15:14:57 -04:00
} ) {
Text ( " Reset adi.pb " )
. fontWeight ( . semibold )
. frame ( maxWidth : . infinity )
2024-08-06 10:43:52 +09:00
}
2024-08-12 15:14:57 -04:00
. buttonStyle ( PlainButtonStyle ( ) )
. padding ( )
. background ( RoundedRectangle ( cornerRadius : 10 ) . fill ( Color . red ) )
. foregroundColor ( . white )
. shadow ( color : Color . red . opacity ( 0.4 ) , radius : 10 , x : 0 , y : 5 )
2024-10-23 08:16:37 +08:00
. alert ( isPresented : $ showingConfirmation ) {
Alert (
title : Text ( " Reset adi.pb " ) ,
message : Text ( " are you sure to clear the adi.pb from keychain? " ) ,
primaryButton : . default ( Text ( " do it " ) ) {
#if ! DEBUG
if Keychain . shared . adiPb != nil {
Keychain . shared . adiPb = nil
}
#endif
print ( " Cleared adi.pb from keychain " )
errorCallback ( )
presentationMode . wrappedValue . dismiss ( )
} ,
secondaryButton : . cancel ( Text ( " cancel " ) ) {
print ( " canceled " )
}
)
}
2024-08-06 10:43:52 +09:00
}
2024-08-12 15:14:57 -04:00
. padding ( . horizontal )
2024-08-12 20:31:13 -04:00
. padding ( . bottom )
2024-08-06 10:43:52 +09:00
}
}
2024-08-12 16:53:45 -04:00
. navigationBarHidden ( true )
. navigationTitle ( " " )
2024-08-06 10:43:52 +09:00
}
}