2019-06-06 14:46:23 -07:00
//
2019-09-05 11:59:10 -07:00
// S e t t i n g s V i e w C o n t r o l l e r . s w i f t
2019-06-06 14:46:23 -07:00
// A l t S t o r e
//
2019-09-05 11:59:10 -07:00
// C r e a t e d b y R i l e y T e s t u t o n 8 / 3 1 / 1 9 .
2019-06-06 14:46:23 -07:00
// C o p y r i g h t © 2 0 1 9 R i l e y T e s t u t . A l l r i g h t s r e s e r v e d .
//
import UIKit
2024-08-06 10:43:52 +09:00
import SwiftUI
2019-09-07 15:34:07 -07:00
import SafariServices
2019-09-12 13:23:21 -07:00
import MessageUI
2020-09-08 16:44:36 -07:00
import Intents
import IntentsUI
2019-06-06 14:46:23 -07:00
2025-02-08 04:45:22 +05:30
import SemanticVersion
2020-09-03 16:39:08 -07:00
import AltStoreCore
2025-06-12 12:47:33 +08:00
import CAltSign
import UniformTypeIdentifiers
2020-09-03 16:39:08 -07:00
2019-09-05 11:59:10 -07:00
extension SettingsViewController
{
fileprivate enum Section : Int , CaseIterable
{
case signIn
case account
case patreon
2024-02-21 14:10:08 -06:00
case display
2020-09-08 16:44:36 -07:00
case appRefresh
2019-09-07 15:34:07 -07:00
case instructions
2022-12-15 15:10:20 -06:00
case techyThings
2019-09-07 15:34:07 -07:00
case credits
2025-01-12 20:48:21 +05:30
case advancedSettings
2025-06-12 12:47:33 +08:00
case signing
2025-01-12 20:48:21 +05:30
// d i a g n o s t i c s s e c t i o n , w i l l b e e n a b l e d o n r e l e a s e b u i l d s o n l y o n s w i p e d o w n w i t h 3 f i n g e r s 3 t i m e s
case diagnostics
2024-12-12 17:51:59 +05:30
// c a s e m a c D i r t y C o w
2019-09-05 11:59:10 -07:00
}
2019-09-07 15:34:07 -07:00
2020-09-08 16:44:36 -07:00
fileprivate enum AppRefreshRow : Int , CaseIterable
{
case backgroundRefresh
2024-12-12 20:03:31 +05:30
case noIdleTimeout
2020-09-08 16:44:36 -07:00
case addToSiri
2024-12-12 20:03:31 +05:30
case disableAppLimit
2020-09-08 16:44:36 -07:00
static var allCases : [ AppRefreshRow ] {
2025-01-12 20:48:21 +05:30
var c : [ AppRefreshRow ] = [ . backgroundRefresh , . noIdleTimeout , . addToSiri ]
2024-12-12 19:12:09 +05:30
// c o n d i t i o n a l e n t r i e s g o a t t h e l a s t t o p r e s e r v e o r d e r i n g
2025-01-19 18:47:25 +05:30
if UserDefaults . standard . isCowExploitSupported || ! ProcessInfo ( ) . sparseRestorePatched
{
c . append ( . disableAppLimit )
}
2024-10-11 01:12:22 -04:00
return c
2020-09-08 16:44:36 -07:00
}
}
2019-09-07 15:34:07 -07:00
fileprivate enum CreditsRow : Int , CaseIterable
{
case developer
2022-04-13 20:06:57 -07:00
case operations
2019-09-07 15:34:07 -07:00
case designer
case softwareLicenses
}
2019-09-12 13:23:21 -07:00
2023-02-07 16:11:39 -06:00
fileprivate enum TechyThingsRow : Int , CaseIterable
{
case errorLog
case clearCache
}
2025-01-12 20:48:21 +05:30
fileprivate enum AdvancedSettingsRow : Int , CaseIterable
2019-09-12 13:23:21 -07:00
{
case sendFeedback
case refreshAttempts
2024-06-17 09:43:25 +10:00
case refreshSideJITServer
2023-01-09 15:15:31 +08:00
case resetPairingFile
2024-08-06 10:43:52 +09:00
case anisetteServers
2025-02-09 17:28:24 +05:30
case betaUpdates
case betaTrack
2025-01-12 20:48:21 +05:30
// c a s e h i d d e n S e t t i n g s
}
2025-06-12 12:47:33 +08:00
fileprivate enum SigningSettingsRow : Int , CaseIterable {
2025-09-07 13:47:04 -04:00
case importAccount
case exportAccount
2025-06-12 12:47:33 +08:00
case importCert
case exportCert
}
2025-01-12 20:48:21 +05:30
fileprivate enum DiagnosticsRow : Int , CaseIterable
{
case responseCaching
2025-01-02 20:05:16 +05:30
case exportResignedApp
2025-01-02 20:48:25 +05:30
case verboseOperationsLogging
2025-02-08 04:45:22 +05:30
case exportDatabase
case deleteDatabase
2025-01-14 07:23:23 +05:30
case operationsLoggingControl
2025-02-09 17:28:24 +05:30
case recreateDatabase
2025-01-14 20:02:33 +05:30
case minimuxerConsoleLogging
2019-09-12 13:23:21 -07:00
}
2019-09-05 11:59:10 -07:00
}
2019-06-06 14:46:23 -07:00
2023-01-04 09:52:12 -05:00
final class SettingsViewController : UITableViewController
2019-06-06 14:46:23 -07:00
{
2019-09-05 11:59:10 -07:00
private var activeTeam : Team ?
2019-06-06 14:46:23 -07:00
2019-09-05 11:59:10 -07:00
private var prototypeHeaderFooterView : SettingsHeaderFooterView !
2019-06-06 14:46:23 -07:00
2025-02-09 17:28:24 +05:30
// A d d o u t l e t
@IBOutlet private var betaTrackLabel : UILabel !
@IBOutlet private var betaTrackPopupButton : UIButton !
2019-09-19 22:20:10 -07:00
private var debugGestureCounter = 0
private weak var debugGestureTimer : Timer ?
2019-09-05 11:59:10 -07:00
@IBOutlet private var accountNameLabel : UILabel !
@IBOutlet private var accountEmailLabel : UILabel !
@IBOutlet private var accountTypeLabel : UILabel !
2019-06-06 14:46:23 -07:00
2019-09-05 11:59:10 -07:00
@IBOutlet private var backgroundRefreshSwitch : UISwitch !
2023-11-28 00:44:47 +09:00
@IBOutlet private var noIdleTimeoutSwitch : UISwitch !
2024-10-11 01:12:22 -04:00
@IBOutlet private var disableAppLimitSwitch : UISwitch !
2025-02-09 17:28:24 +05:30
@IBOutlet private var betaUpdatesSwitch : UISwitch !
2025-01-02 20:48:25 +05:30
@IBOutlet private var exportResignedAppsSwitch : UISwitch !
@IBOutlet private var verboseOperationsLoggingSwitch : UISwitch !
2025-01-14 20:02:33 +05:30
@IBOutlet private var minimuxerConsoleLoggingSwitch : UISwitch !
2019-06-06 14:46:23 -07:00
2025-01-14 20:02:33 +05:30
// @ I B O u t l e t p r i v a t e v a r r e f r e s h S i d e J I T S e r v e r : U I L a b e l !
2023-05-29 12:10:44 -05:00
@IBOutlet private var disableResponseCachingSwitch : UISwitch !
2024-06-17 09:43:25 +10:00
2024-02-14 15:50:09 -06:00
@IBOutlet private var mastodonButton : UIButton !
@IBOutlet private var threadsButton : UIButton !
@IBOutlet private var twitterButton : UIButton !
@IBOutlet private var githubButton : UIButton !
2020-01-13 13:32:55 -08:00
@IBOutlet private var versionLabel : UILabel !
2025-02-09 17:28:24 +05:30
2025-02-08 04:45:22 +05:30
@IBOutlet private var recreateDatabaseSwitch : UISwitch !
2020-01-13 13:32:55 -08:00
2019-10-24 13:04:30 -07:00
override var preferredStatusBarStyle : UIStatusBarStyle {
return . lightContent
}
2025-02-08 04:45:22 +05:30
private static var exportDBInProgress = false
2025-02-09 17:28:24 +05:30
private static var deleteDBInProgress = false
2025-01-02 20:05:16 +05:30
2019-09-19 14:43:26 -07:00
required init ? ( coder aDecoder : NSCoder )
{
super . init ( coder : aDecoder )
NotificationCenter . default . addObserver ( self , selector : #selector ( SettingsViewController . openPatreonSettings ( _ : ) ) , name : AppDelegate . openPatreonSettingsDeepLinkNotification , object : nil )
2024-08-06 10:43:52 +09:00
NotificationCenter . default . addObserver ( self , selector : #selector ( SettingsViewController . openErrorLog ( _ : ) ) , name : ToastView . openErrorLogNotification , object : nil )
2025-04-20 08:48:50 +08:00
NotificationCenter . default . addObserver ( self , selector : #selector ( SettingsViewController . openExportCertificateConfirm ( _ : ) ) , name : AppDelegate . exportCertificateNotification , object : nil )
2019-09-19 14:43:26 -07:00
}
2025-02-09 17:28:24 +05:30
private func handleReleaseChannelSelection ( _ channel : String ) {
// U p d a t e y o u r m o d e l / p r e f e r e n c e s
UserDefaults . standard . betaUdpatesTrack = channel
updateReleaseChannelButtonTitle ( )
}
private func updateReleaseChannelButtonTitle ( ) {
let channel = UserDefaults . standard . betaUdpatesTrack ? ? UserDefaults . defaultBetaUpdatesTrack
betaTrackPopupButton . setTitle ( channel , for : . normal )
}
private func configureReleaseChannelButton ( ) {
let currentTrack = UserDefaults . standard . betaUdpatesTrack
// g e t a l l t r a c k s a s s t r i n g a v a i l a b l e e x c e p t . s t a b l e a n d . u n k n o w n
var trackOptions : [ String ] = ReleaseTracks . betaTracks . map { $0 . rawValue }
if let currentTrack {
// p r e p e n d c u r r e n t l y s e l e c t e d b e t a t r a c k f r o m t h e u s e r d e f a u l t s
trackOptions = [ currentTrack ] + trackOptions . filter { $0 != currentTrack }
}
// C r e a t e m e n u i t e m s w i t h p r o p e r s t y l i n g
let items = trackOptions . map { channel in
UIAction ( title : channel , handler : { [ weak self ] _ in
self ? . handleReleaseChannelSelection ( channel )
} )
}
// C r e a t e m e n u w i t h p r o p e r s t y l i n g
let menu = UIMenu ( title : " " ,
options : [ . singleSelection , . displayInline ] , // A d d d i s p l a y I n l i n e
children : items
)
betaTrackPopupButton . menu = menu
// S e t i n i t i a l s t a t e
updateReleaseChannelButtonTitle ( )
}
2019-06-06 14:46:23 -07:00
override func viewDidLoad ( )
{
super . viewDidLoad ( )
2025-10-15 20:22:35 +05:30
// - - - i O S 2 6 f i x - - -
if #available ( iOS 26.0 , * ) {
let appearance = UINavigationBarAppearance ( )
// a p p e a r a n c e . c o n f i g u r e W i t h O p a q u e B a c k g r o u n d ( ) / / o r . d e f a u l t B a c k g r o u n d i f y o u w a n t b l u r
// a p p e a r a n c e . b a c k g r o u n d C o l o r = U I C o l o r ( n a m e d : " S e t t i n g s B a c k g r o u n d " )
appearance . titleTextAttributes = [ . foregroundColor : UIColor . white ]
appearance . largeTitleTextAttributes = [ . foregroundColor : UIColor . white ]
navigationController ? . navigationBar . standardAppearance = appearance
navigationController ? . navigationBar . scrollEdgeAppearance = appearance // r e q u i r e d f o r i O S 2 6 , m a y b e e n f o r c e i t i n s t o r y b o a r d ?
}
2019-09-05 11:59:10 -07:00
let nib = UINib ( nibName : " SettingsHeaderFooterView " , bundle : nil )
self . prototypeHeaderFooterView = nib . instantiate ( withOwner : nil , options : nil ) [ 0 ] as ? SettingsHeaderFooterView
self . tableView . register ( nib , forHeaderFooterViewReuseIdentifier : " HeaderFooterView " )
2025-01-12 20:48:21 +05:30
let debugModeGestureRecognizer = UISwipeGestureRecognizer ( target : self , action : #selector ( SettingsViewController . handleDebugModeGesture ( _ : ) ) )
debugModeGestureRecognizer . delegate = self
debugModeGestureRecognizer . direction = . up
debugModeGestureRecognizer . numberOfTouchesRequired = 3
self . tableView . addGestureRecognizer ( debugModeGestureRecognizer )
// s e t t h e v e r s i o n l a b e l t o s h o w i n s e t t i n g s s c r e e n
self . versionLabel . text = getVersionLabel ( )
2024-08-06 10:43:52 +09:00
self . versionLabel . numberOfLines = 0
self . versionLabel . lineBreakMode = . byWordWrapping
self . versionLabel . setNeedsUpdateConstraints ( )
2024-04-23 03:20:40 -04:00
self . tableView . contentInset . bottom = 40
2020-01-24 11:34:26 -08:00
2019-06-06 14:46:23 -07:00
self . update ( )
2021-10-06 12:16:47 -07:00
2024-02-14 15:50:09 -06:00
if #available ( iOS 15 , * )
2021-10-06 12:16:47 -07:00
{
2024-02-14 15:50:09 -06:00
if let appearance = self . tabBarController ? . tabBar . standardAppearance
{
appearance . stackedLayoutAppearance . normal . badgeBackgroundColor = . altPrimary
self . navigationController ? . tabBarItem . scrollEdgeAppearance = appearance
}
// W e c a n o n l y c o n f i g u r e t h e c o n t e n t M o d e f o r a b u t t o n ' s b a c k g r o u n d i m a g e f r o m I n t e r f a c e B u i l d e r .
// T h i s w o r k s , b u t i t m e a n s b u t t o n s d o n ' t v i s u a l l y h i g h l i g h t b e c a u s e t h e r e ' s n o f o r e g r o u n d i m a g e .
// A s a w o r k a r o u n d , w e m a n u a l l y s e t t h e f o r e g r o u n d i m a g e + c o n t e n t M o d e h e r e .
for button in [ self . mastodonButton ! , self . threadsButton ! , self . twitterButton ! , self . githubButton ! ]
{
// G e t t h e a s s i g n e d i m a g e f r o m I n t e r f a c e B u i l d e r .
let image = button . configuration ? . background . image
button . configuration = nil
button . setImage ( image , for : . normal )
button . imageView ? . contentMode = . scaleAspectFit
}
2021-10-06 12:16:47 -07:00
}
2025-02-09 17:28:24 +05:30
configureReleaseChannelButton ( )
2025-11-16 00:46:51 +05:30
#if ! targetEnvironment ( simulator )
2025-09-07 13:47:04 -04:00
detectAndImportAccountFile ( )
2025-11-16 00:46:51 +05:30
#endif
2025-09-07 13:47:04 -04:00
}
func importAccountAtFile ( _ file : URL , remove : Bool = false ) {
2025-11-07 16:52:55 -05:00
_ = file . startAccessingSecurityScopedResource ( )
defer { file . stopAccessingSecurityScopedResource ( ) }
guard let accountD = try ? Data ( contentsOf : file ) else {
let toastView = ToastView ( text : NSLocalizedString ( " Could not read data from file! " , comment : " " ) , detailText : " \( file ) " )
return toastView . show ( in : self )
}
guard let account = try ? Foundation . JSONDecoder ( ) . decode ( ImportedAccount . self , from : accountD ) else {
let toastView = ToastView ( text : NSLocalizedString ( " Could not parse data from file! " , comment : " " ) , detailText : " \( file ) " )
return toastView . show ( in : self )
}
print ( " We want to import this account probably: \( account ) " )
if remove {
try ? FileManager . default . removeItem ( at : file )
}
Keychain . shared . appleIDEmailAddress = account . email
Keychain . shared . appleIDPassword = account . password
Keychain . shared . adiPb = account . adiPB
Keychain . shared . identifier = account . local_user
signIn ( )
update ( )
if let altCert = ALTCertificate ( p12Data : account . cert , password : account . certpass ) {
Keychain . shared . signingCertificate = altCert . encryptedP12Data ( withPassword : " " ) !
Keychain . shared . signingCertificatePassword = account . certpass
let toastView = ToastView ( text : NSLocalizedString ( " Successfully imported ' \( account . email ) '! " , comment : " " ) , detailText : " SideStore should be fully operational! " )
return toastView . show ( in : self )
} else {
let toastView = ToastView ( text : NSLocalizedString ( " Failed to import account certificate! " , comment : " " ) , detailText : " Failed to create ALTCertificate. Check if the password is correct. Still imported account/adi.pb details! " )
return toastView . show ( in : self )
2025-09-07 13:47:04 -04:00
}
}
func detectAndImportAccountFile ( ) {
let accountFileURL = FileManager . default . documentsDirectory . appendingPathComponent ( " Account.sideconf " )
#if ! DEBUG
importAccountAtFile ( accountFileURL , remove : true )
#else
importAccountAtFile ( accountFileURL )
#endif
}
func exportAccount ( _ certpass : String ) -> ImportedAccount ? {
guard let email = Keychain . shared . appleIDEmailAddress ,
let password = Keychain . shared . appleIDPassword ,
let cert = Keychain . shared . signingCertificate ,
let identifier = Keychain . shared . identifier ,
let adiPB = Keychain . shared . adiPb else {
#if DEBUG
print ( Keychain . shared . appleIDEmailAddress ? ? " Empty email " )
print ( Keychain . shared . appleIDPassword ? ? " Empty password " )
print ( Keychain . shared . signingCertificate ? ? " Empty cert " )
print ( Keychain . shared . identifier ? ? " Empty identifier " )
print ( Keychain . shared . adiPb ? ? " Empty adiPb " )
#endif
return nil
}
2025-11-07 16:52:55 -05:00
return ImportedAccount ( email : email , password : password , cert : cert , certpass : certpass , local_user : identifier , adiPB : adiPB )
2025-09-07 13:47:04 -04:00
}
func showExportAccount ( ) {
Task {
2025-11-07 16:52:55 -05:00
guard let password = await withUnsafeContinuation ( { ( c : UnsafeContinuation < String ? , Never > ) in
2025-09-07 13:47:04 -04:00
let alertController = UIAlertController ( title : NSLocalizedString ( " Please enter the password for the certificate. " , comment : " " ) , message : nil , preferredStyle : . alert )
alertController . addTextField { ( textField ) in
textField . autocorrectionType = . no
textField . autocapitalizationType = . none
}
let submitAction = UIAlertAction ( title : NSLocalizedString ( " OK " , comment : " " ) , style : . default ) { ( action ) in
let textField = alertController . textFields ? . first
let code = textField ? . text ? ? " "
c . resume ( returning : code )
}
alertController . addAction ( submitAction )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Cancel " , comment : " " ) , style : . cancel ) { ( action ) in
c . resume ( returning : nil )
} )
self . present ( alertController , animated : true )
2025-11-07 16:52:55 -05:00
} ) else {
2025-09-07 13:47:04 -04:00
return
}
guard let account = exportAccount ( password ) else {
let toastView = ToastView ( text : NSLocalizedString ( " Failed to export account! " , comment : " " ) , detailText : " Account not found. " )
2025-11-07 16:52:55 -05:00
return toastView . show ( in : self )
2025-09-07 13:47:04 -04:00
}
2025-11-07 16:52:55 -05:00
guard let accountData = try ? Foundation . JSONEncoder ( ) . encode ( account ) else {
2025-09-07 13:47:04 -04:00
let toastView = ToastView ( text : NSLocalizedString ( " Failed to export account data! " , comment : " " ) , detailText : " Account malformed. " )
toastView . show ( in : self )
return
}
let accountTmpPath = FileManager . default . temporaryDirectory . appendingPathComponent ( " \( account . email ) .sideconf " )
do {
try accountData . write ( to : accountTmpPath )
} catch {
let toastView = ToastView ( text : NSLocalizedString ( " Failed to export account! " , comment : " " ) , detailText : error . localizedDescription )
toastView . show ( in : self )
return
}
let exportVC = UIDocumentPickerViewController ( forExporting : [ accountTmpPath ] , asCopy : false )
self . present ( exportVC , animated : true )
}
2019-06-06 14:46:23 -07:00
}
2019-09-07 15:34:07 -07:00
override func viewWillAppear ( _ animated : Bool )
{
super . viewWillAppear ( animated )
2024-12-15 23:00:12 +05:30
// s h o w n a v b a r i f n o t s h o w n a l r e a d y
self . navigationController ? . setNavigationBarHidden ( false , animated : animated )
2019-09-07 15:34:07 -07:00
self . update ( )
}
2024-08-06 10:43:52 +09:00
override func prepare ( for segue : UIStoryboardSegue , sender : Any ? ) {
if segue . identifier = = " anisetteServers " {
2024-12-15 23:00:12 +05:30
let controller = segue . destination
// d i s a b l e b o t t o m t a b b a r s i n c e ' b a c k ' b u t t o n i s a l r e a d y a v a i l a b l e
// c o n t r o l l e r . h i d e s B o t t o m B a r W h e n P u s h e d = t r u e
2024-08-06 10:43:52 +09:00
self . show ( controller , sender : nil )
} else {
super . prepare ( for : segue , sender : sender )
}
}
2019-06-06 14:46:23 -07:00
}
2025-02-08 04:45:22 +05:30
2019-07-31 11:46:26 -07:00
private extension SettingsViewController
2019-06-06 14:46:23 -07:00
{
2025-01-12 20:48:21 +05:30
private func getVersionLabel ( ) -> String {
2025-01-22 01:47:21 +05:30
let buildInfo = BuildInfo ( )
2025-01-12 20:48:21 +05:30
2025-01-13 03:40:26 +05:30
func getXcodeVersion ( ) -> String {
2025-01-22 01:47:21 +05:30
var xcodeVersion = buildInfo . xcode . map { version in
" Xcode \( version ) " + ( buildInfo . xcode_revision . map { revision in " - \( revision ) " } ? ? " " ) // E x : " 0 . 6 . 0 - X c o d e 1 6 . 2 - 2 1 a c 1 e f "
2025-01-13 03:40:26 +05:30
} ? ? " "
if let pairing = Bundle . main . object ( forInfoDictionaryKey : " ALTPairingFile " ) as ? String ,
pairing != " <insert pairing file here> " {
xcodeVersion += " - true "
}
return xcodeVersion
}
2025-01-22 01:47:21 +05:30
2025-01-13 03:40:26 +05:30
var versionLabel : String = " "
2025-02-08 04:45:22 +05:30
let installedApp = InstalledApp . fetchAltStore ( in : DatabaseManager . shared . viewContext )
// f i r s t c h e c k i f t h e r e i s i n s t a l l e d a p p e n t i t y , i f s o , g e t v e r s i o n i n f o f r o m t h a t
if let installedApp
2025-01-12 20:48:21 +05:30
{
2025-01-13 03:40:26 +05:30
var localizedVersion = installedApp . version
2025-02-08 04:45:22 +05:30
// O n l y s h o w b u i l d v e r s i o n f o r n o n s t a b l e b u i l d s .
localizedVersion += buildInfo . project_version . map { version in
version . isEmpty ? " " : " ( \( version ) ) "
} ? ? installedApp . localizedVersion
2025-02-09 17:28:24 +05:30
2025-01-13 03:40:26 +05:30
versionLabel = NSLocalizedString ( String ( format : " Version %@ " , localizedVersion ) , comment : " SideStore Version " )
2025-01-12 20:48:21 +05:30
}
2025-02-09 16:18:41 +05:30
else if let version = buildInfo . marketing_version
2025-01-12 20:48:21 +05:30
{
2025-01-13 03:40:26 +05:30
versionLabel = NSLocalizedString ( String ( format : " Version %@ " , version ) , comment : " SideStore Version " )
2025-01-12 20:48:21 +05:30
}
else
{
var version = " SideStore \t "
version += " \n \( Bundle . Info . appbundleIdentifier ) "
2025-01-13 03:40:26 +05:30
versionLabel = NSLocalizedString ( version , comment : " SideStore Version " )
2025-01-12 20:48:21 +05:30
}
2025-01-13 03:40:26 +05:30
2025-01-22 01:47:21 +05:30
// a d d x c o d e b u i l d v e r s i o n f o r l o c a l b u i l d s
2025-02-08 04:45:22 +05:30
if let installedApp ,
2025-02-09 16:18:41 +05:30
SemanticVersion ( installedApp . version ) ? . preRelease = = " local "
2025-01-22 01:47:21 +05:30
{
versionLabel += " \n \( getXcodeVersion ( ) ) "
}
2025-02-09 17:28:24 +05:30
2025-01-13 03:40:26 +05:30
return versionLabel
2025-01-12 20:48:21 +05:30
}
2019-06-06 14:46:23 -07:00
func update ( )
{
if let team = DatabaseManager . shared . activeTeam ( )
{
2019-09-05 11:59:10 -07:00
self . accountNameLabel . text = team . name
2019-06-06 14:46:23 -07:00
self . accountEmailLabel . text = team . account . appleID
2019-09-05 11:59:10 -07:00
self . accountTypeLabel . text = team . type . localizedDescription
2019-06-06 14:46:23 -07:00
2019-09-05 11:59:10 -07:00
self . activeTeam = team
2019-06-06 14:46:23 -07:00
}
else
{
2019-09-05 11:59:10 -07:00
self . activeTeam = nil
2019-06-06 14:46:23 -07:00
}
2025-01-12 20:48:21 +05:30
// A p p R e f r e s h R o w
2019-09-05 11:59:10 -07:00
self . backgroundRefreshSwitch . isOn = UserDefaults . standard . isBackgroundRefreshEnabled
2023-11-28 12:00:20 +09:00
self . noIdleTimeoutSwitch . isOn = UserDefaults . standard . isIdleTimeoutDisableEnabled
2024-10-11 01:12:22 -04:00
self . disableAppLimitSwitch . isOn = UserDefaults . standard . isAppLimitDisabled
2025-01-12 20:48:21 +05:30
// A d v a n c e d S e t t i n g s R o w
2025-02-09 17:28:24 +05:30
self . betaUpdatesSwitch . isOn = UserDefaults . standard . isBetaUpdatesEnabled
self . betaTrackLabel . isEnabled = UserDefaults . standard . isBetaUpdatesEnabled
self . betaTrackPopupButton . isEnabled = UserDefaults . standard . isBetaUpdatesEnabled
2025-01-12 20:48:21 +05:30
// D i a g n o s t i c s R o w
self . disableResponseCachingSwitch . isOn = UserDefaults . standard . responseCachingDisabled
2025-01-02 20:48:25 +05:30
self . exportResignedAppsSwitch . isOn = UserDefaults . standard . isExportResignedAppEnabled
self . verboseOperationsLoggingSwitch . isOn = UserDefaults . standard . isVerboseOperationsLoggingEnabled
2025-01-14 20:02:33 +05:30
self . minimuxerConsoleLoggingSwitch . isOn = UserDefaults . standard . isMinimuxerConsoleLoggingEnabled
2024-12-17 21:01:33 +05:30
2025-02-08 04:45:22 +05:30
self . recreateDatabaseSwitch . isOn = UserDefaults . standard . recreateDatabaseOnNextStart
2019-06-06 14:46:23 -07:00
if self . isViewLoaded
{
self . tableView . reloadData ( )
}
}
2019-09-05 11:59:10 -07:00
func prepare ( _ settingsHeaderFooterView : SettingsHeaderFooterView , for section : Section , isHeader : Bool )
{
settingsHeaderFooterView . primaryLabel . isHidden = ! isHeader
settingsHeaderFooterView . secondaryLabel . isHidden = isHeader
settingsHeaderFooterView . button . isHidden = true
settingsHeaderFooterView . layoutMargins . bottom = isHeader ? 0 : 8
switch section
{
case . signIn :
if isHeader
{
settingsHeaderFooterView . primaryLabel . text = NSLocalizedString ( " ACCOUNT " , comment : " " )
}
else
{
2022-09-14 04:45:33 -07:00
settingsHeaderFooterView . secondaryLabel . text = NSLocalizedString ( " Sign in with your Apple ID to download apps from SideStore. " , comment : " " )
2019-09-05 11:59:10 -07:00
}
case . patreon :
2019-09-25 12:43:32 -07:00
if isHeader
{
2023-06-13 23:14:01 -07:00
settingsHeaderFooterView . primaryLabel . text = NSLocalizedString ( " SUPPORT US " , comment : " " )
2019-09-25 12:43:32 -07:00
}
2022-09-14 04:27:26 -07:00
else
{
2023-06-13 23:14:01 -07:00
settingsHeaderFooterView . secondaryLabel . text = NSLocalizedString ( " Support the SideStore Team by following our socials or becoming a patron! " , comment : " " )
2022-09-14 04:27:26 -07:00
}
2022-09-14 04:45:33 -07:00
2019-09-05 11:59:10 -07:00
case . account :
settingsHeaderFooterView . primaryLabel . text = NSLocalizedString ( " ACCOUNT " , comment : " " )
settingsHeaderFooterView . button . setTitle ( NSLocalizedString ( " SIGN OUT " , comment : " " ) , for : . normal )
settingsHeaderFooterView . button . addTarget ( self , action : #selector ( SettingsViewController . signOut ( _ : ) ) , for : . primaryActionTriggered )
settingsHeaderFooterView . button . isHidden = false
2020-09-08 16:44:36 -07:00
case . appRefresh :
if isHeader
{
settingsHeaderFooterView . primaryLabel . text = NSLocalizedString ( " REFRESHING APPS " , comment : " " )
}
else
{
2024-11-11 21:12:49 +01:00
settingsHeaderFooterView . secondaryLabel . text = NSLocalizedString ( " Enable Background Refresh to automatically refresh apps in the background when connected to Wi-Fi. \n \n Enable Disable Idle Timeout to allow SideStore to keep your device awake during a refresh or install of any apps. " , comment : " " )
2020-09-08 16:44:36 -07:00
}
2019-09-05 11:59:10 -07:00
2024-02-21 14:10:08 -06:00
case . display :
if isHeader
{
settingsHeaderFooterView . primaryLabel . text = NSLocalizedString ( " DISPLAY " , comment : " " )
}
else
{
2024-12-11 15:20:10 -05:00
settingsHeaderFooterView . secondaryLabel . text = NSLocalizedString ( " Personalize your SideStore experience by choosing an alternate app icon. " , comment : " " )
2024-02-21 14:10:08 -06:00
}
2019-09-07 15:34:07 -07:00
case . instructions :
break
2022-12-15 15:10:20 -06:00
case . techyThings :
2023-02-08 13:50:29 -06:00
if isHeader
{
settingsHeaderFooterView . primaryLabel . text = NSLocalizedString ( " TECHY THINGS " , comment : " " )
}
else
{
settingsHeaderFooterView . secondaryLabel . text = NSLocalizedString ( " Free up disk space by removing non-essential data, such as temporary files and backups for uninstalled apps. " , comment : " " )
}
2022-12-15 15:10:20 -06:00
2019-09-07 15:34:07 -07:00
case . credits :
settingsHeaderFooterView . primaryLabel . text = NSLocalizedString ( " CREDITS " , comment : " " )
2025-01-12 20:48:21 +05:30
case . advancedSettings :
settingsHeaderFooterView . primaryLabel . text = NSLocalizedString ( " ADVANCED SETTINGS " , comment : " " )
2025-06-12 12:47:33 +08:00
case . signing :
// FIXME: W h y " E n a b l e B a c k g r o u n d R e f r e s h . . . " a p p e a r h e r e i f s e c o n d a r y L a b e l i s n o t s p e c i f i e d ? ? ?
if isHeader
{
settingsHeaderFooterView . primaryLabel . text = NSLocalizedString ( " SIGNING " , comment : " " )
}
else
{
settingsHeaderFooterView . secondaryLabel . text = NSLocalizedString ( " " , comment : " " )
}
2025-01-12 20:48:21 +05:30
2025-06-12 12:47:33 +08:00
2025-01-12 20:48:21 +05:30
case . diagnostics :
settingsHeaderFooterView . primaryLabel . text = NSLocalizedString ( " DIAGNOSTICS " , comment : " " )
2024-12-12 17:51:59 +05:30
// c a s e . m a c D i r t y C o w :
// i f i s H e a d e r
// {
// s e t t i n g s H e a d e r F o o t e r V i e w . p r i m a r y L a b e l . t e x t = N S L o c a l i z e d S t r i n g ( " M A C D I R T Y C O W " , c o m m e n t : " " )
// }
// e l s e
// {
// s e t t i n g s H e a d e r F o o t e r V i e w . s e c o n d a r y L a b e l . t e x t = N S L o c a l i z e d S t r i n g ( " I f y o u ' v e r e m o v e d t h e 3 - s i d e l o a d e d a p p l i m i t v i a t h e M a c D i r t y C o w e x p l o i t , d i s a b l e t h i s s e t t i n g t o s i d e l o a d m o r e t h a n 3 a p p s a t a t i m e . " , c o m m e n t : " " )
// }
2023-02-06 17:36:05 -06:00
2019-09-05 11:59:10 -07:00
}
}
func preferredHeight ( for settingsHeaderFooterView : SettingsHeaderFooterView , in section : Section , isHeader : Bool ) -> CGFloat
{
let widthConstraint = settingsHeaderFooterView . contentView . widthAnchor . constraint ( equalToConstant : tableView . bounds . width )
NSLayoutConstraint . activate ( [ widthConstraint ] )
defer { NSLayoutConstraint . deactivate ( [ widthConstraint ] ) }
self . prepare ( settingsHeaderFooterView , for : section , isHeader : isHeader )
let size = settingsHeaderFooterView . contentView . systemLayoutSizeFitting ( UIView . layoutFittingCompressedSize )
return size . height
}
2023-02-06 17:36:05 -06:00
func isSectionHidden ( _ section : Section ) -> Bool
{
switch section
{
2024-12-12 17:51:59 +05:30
// c a s e . m a c D i r t y C o w :
// l e t i s H i d d e n = ! ( U s e r D e f a u l t s . s t a n d a r d . i s C o w E x p l o i t S u p p o r t e d & & U s e r D e f a u l t s . s t a n d a r d . i s D e b u g M o d e E n a b l e d )
// r e t u r n i s H i d d e n
2023-02-06 17:36:05 -06:00
default : return false
}
}
2019-06-06 14:46:23 -07:00
}
2019-07-31 11:46:26 -07:00
private extension SettingsViewController
2019-06-06 14:46:23 -07:00
{
2019-09-05 11:59:10 -07:00
func signIn ( )
2019-06-06 14:46:23 -07:00
{
AppManager . shared . authenticate ( presentingViewController : self ) { ( result ) in
DispatchQueue . main . async {
2019-11-18 14:49:17 -08:00
switch result
{
case . failure ( OperationError . cancelled ) :
// I g n o r e
break
case . failure ( let error ) :
2020-03-30 14:07:18 -07:00
let toastView = ToastView ( error : error )
toastView . show ( in : self )
2019-11-18 14:49:17 -08:00
case . success : break
}
2019-06-06 14:46:23 -07:00
self . update ( )
}
}
}
2019-09-05 11:59:10 -07:00
@objc func signOut ( _ sender : UIBarButtonItem )
2019-06-06 14:46:23 -07:00
{
func signOut ( )
{
DatabaseManager . shared . signOut { ( error ) in
DispatchQueue . main . async {
if let error = error
{
2020-03-30 14:07:18 -07:00
let toastView = ToastView ( error : error )
toastView . show ( in : self )
2019-06-06 14:46:23 -07:00
}
self . update ( )
}
}
}
let alertController = UIAlertController ( title : NSLocalizedString ( " Are you sure you want to sign out? " , comment : " " ) , message : NSLocalizedString ( " You will no longer be able to install or refresh apps once you sign out. " , comment : " " ) , preferredStyle : . actionSheet )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Sign Out " , comment : " " ) , style : . destructive ) { _ in signOut ( ) } )
alertController . addAction ( . cancel )
2023-01-09 15:15:31 +08:00
// F i x c r a s h o n i P a d
alertController . popoverPresentationController ? . barButtonItem = sender
2019-06-06 14:46:23 -07:00
self . present ( alertController , animated : true , completion : nil )
}
2019-09-05 11:59:10 -07:00
2024-10-11 01:48:01 -04:00
@IBAction func toggleDisableAppLimit ( _ sender : UISwitch ) {
2025-01-19 18:47:25 +05:30
if UserDefaults . standard . isCowExploitSupported || ! ProcessInfo ( ) . sparseRestorePatched {
// a c c e p t s t a t e c h a n g e o n l y w h e n v a l i d
UserDefaults . standard . isAppLimitDisabled = sender . isOn
// TODO: H e r e w e f o r c e r e l o a d t h e a c t i v e A p p s L i m i t a f t e r d e t e c t i n g c h a n g e i n i s A p p L i m i t D i s a b l e d
// W h y d o w e n e e d t o d o t h i s , o n c e i d e n t i f i e d i f t h i s i s i n t e n t i o n a l a n d w o r k i n g a s e x p e c t e d , r e m o v e t h i s t o d o
if UserDefaults . standard . activeAppsLimit != nil
{
UserDefaults . standard . activeAppsLimit = InstalledApp . freeAccountActiveAppsLimit
}
2024-12-07 17:45:09 +05:30
}
2024-10-11 01:48:01 -04:00
}
2024-12-26 04:26:43 +05:30
@IBAction func toggleResignedAppExport ( _ sender : UISwitch ) {
// u p d a t e i t i n d a t a b a s e
2025-01-02 20:05:16 +05:30
UserDefaults . standard . isExportResignedAppEnabled = sender . isOn
2024-12-26 04:26:43 +05:30
}
2025-01-02 20:48:25 +05:30
@IBAction func toggleVerboseOperationsLogging ( _ sender : UISwitch ) {
// u p d a t e i t i n d a t a b a s e
UserDefaults . standard . isVerboseOperationsLoggingEnabled = sender . isOn
}
2025-01-14 20:02:33 +05:30
@IBAction func toggleMinimuxerConsoleLogging ( _ sender : UISwitch ) {
// u p d a t e i t i n d a t a b a s e
UserDefaults . standard . isMinimuxerConsoleLoggingEnabled = sender . isOn
}
2025-04-03 00:49:07 -07:00
@IBAction func toggleMinimuxerStatusCheck ( _ sender : UISwitch ) {
// u p d a t e i t i n d a t a b a s e
UserDefaults . standard . isMinimuxerStatusCheckEnabled = sender . isOn
}
2025-02-08 04:45:22 +05:30
@IBAction func toggleRecreateDatabaseSwitch ( _ sender : UISwitch ) {
// U p d a t e t h e s e t t i n g i n U s e r D e f a u l t s
UserDefaults . standard . recreateDatabaseOnNextStart = sender . isOn
guard sender . isOn else { return }
DispatchQueue . global ( ) . async {
for time in ( 1. . . 3 ) . reversed ( ) {
DispatchQueue . main . async {
guard UserDefaults . standard . recreateDatabaseOnNextStart else {
return
}
let toast = ToastView ( text : " Database Delete Scheduled on Next Launch " , detailText : " App is closing in \( time ) seconds... " )
toast . tintColor = . altPrimary
toast . preferredDuration = 1
toast . show ( in : self )
}
sleep ( 1 ) // B a c k g r o u n d s l e e p
}
DispatchQueue . main . async {
guard UserDefaults . standard . recreateDatabaseOnNextStart else {
return
}
exit ( 0 )
}
}
2024-12-17 21:01:33 +05:30
}
2025-02-08 04:45:22 +05:30
2024-12-17 21:01:33 +05:30
2025-02-09 17:28:24 +05:30
@IBAction func toggleEnableBetaUpdates ( _ sender : UISwitch ) {
betaTrackLabel . isEnabled = sender . isOn
betaTrackPopupButton . isEnabled = sender . isOn
// u p d a t e i t i n d a t a b a s e
UserDefaults . standard . isBetaUpdatesEnabled = sender . isOn
}
2019-09-05 11:59:10 -07:00
@IBAction func toggleIsBackgroundRefreshEnabled ( _ sender : UISwitch )
{
UserDefaults . standard . isBackgroundRefreshEnabled = sender . isOn
}
2019-09-19 22:20:10 -07:00
2023-11-28 00:44:47 +09:00
@IBAction func toggleNoIdleTimeoutEnabled ( _ sender : UISwitch )
{
UserDefaults . standard . isIdleTimeoutDisableEnabled = sender . isOn
}
2023-05-29 12:10:44 -05:00
@IBAction func toggleDisableResponseCaching ( _ sender : UISwitch )
{
UserDefaults . standard . responseCachingDisabled = sender . isOn
}
2025-01-12 20:48:21 +05:30
func addRefreshAppsShortcut ( )
2020-09-08 16:44:36 -07:00
{
guard let shortcut = INShortcut ( intent : INInteraction . refreshAllApps ( ) . intent ) else { return }
let viewController = INUIAddVoiceShortcutViewController ( shortcut : shortcut )
viewController . delegate = self
viewController . modalPresentationStyle = . formSheet
self . present ( viewController , animated : true , completion : nil )
}
2023-02-07 16:11:39 -06:00
func clearCache ( )
{
2023-08-30 05:12:02 +00:00
let alertController = UIAlertController ( title : NSLocalizedString ( " Are you sure you want to clear SideStore's cache? " , comment : " " ) ,
2023-02-07 16:11:39 -06:00
message : NSLocalizedString ( " This will remove all temporary files as well as backups for uninstalled apps. " , comment : " " ) ,
preferredStyle : . actionSheet )
alertController . addAction ( UIAlertAction ( title : UIAlertAction . cancel . title , style : UIAlertAction . cancel . style ) { [ weak self ] _ in
self ? . tableView . indexPathForSelectedRow . map { self ? . tableView . deselectRow ( at : $0 , animated : true ) }
} )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Clear Cache " , comment : " " ) , style : . destructive ) { [ weak self ] _ in
AppManager . shared . clearAppCache { result in
DispatchQueue . main . async {
self ? . tableView . indexPathForSelectedRow . map { self ? . tableView . deselectRow ( at : $0 , animated : true ) }
switch result
{
case . success : break
case . failure ( let error ) :
let alertController = UIAlertController ( title : NSLocalizedString ( " Unable to Clear Cache " , comment : " " ) , message : error . localizedDescription , preferredStyle : . alert )
alertController . addAction ( . ok )
self ? . present ( alertController , animated : true )
}
}
}
} )
2023-11-26 13:50:45 +09:00
if let popoverController = alertController . popoverPresentationController {
popoverController . sourceView = self . view
popoverController . sourceRect = CGRect ( x : self . view . bounds . midX , y : self . view . bounds . midY , width : 0 , height : 0 )
}
2023-02-07 16:11:39 -06:00
self . present ( alertController , animated : true )
}
2019-09-19 22:20:10 -07:00
@IBAction func handleDebugModeGesture ( _ gestureRecognizer : UISwipeGestureRecognizer )
{
self . debugGestureCounter += 1
self . debugGestureTimer ? . invalidate ( )
if self . debugGestureCounter >= 3
{
self . debugGestureCounter = 0
UserDefaults . standard . isDebugModeEnabled . toggle ( )
self . tableView . reloadData ( )
}
else
{
self . debugGestureTimer = Timer . scheduledTimer ( withTimeInterval : 0.4 , repeats : false ) { [ weak self ] ( timer ) in
self ? . debugGestureCounter = 0
}
}
}
2019-09-21 13:57:18 -07:00
func openTwitter ( username : String )
{
let twitterAppURL = URL ( string : " twitter://user?screen_name= " + username ) !
UIApplication . shared . open ( twitterAppURL , options : [ : ] ) { ( success ) in
if success
{
if let selectedIndexPath = self . tableView . indexPathForSelectedRow
{
self . tableView . deselectRow ( at : selectedIndexPath , animated : true )
}
}
else
{
let safariURL = URL ( string : " https://twitter.com/ " + username ) !
let safariViewController = SFSafariViewController ( url : safariURL )
safariViewController . preferredControlTintColor = . altPrimary
self . present ( safariViewController , animated : true , completion : nil )
}
}
}
2024-02-14 15:50:09 -06:00
func openMastodon ( username : String )
{
// R e l y o n u n i v e r s a l l i n k s t o o p e n a p p .
let components = username . split ( separator : " @ " )
guard components . count = = 2 else { return }
let server = String ( components [ 1 ] )
let username = " @ " + String ( components [ 0 ] )
guard let serverURL = URL ( string : " https:// " + server ) else { return }
let mastodonURL = serverURL . appendingPathComponent ( username )
UIApplication . shared . open ( mastodonURL , options : [ : ] )
}
func openThreads ( username : String )
{
// R e l y o n u n i v e r s a l l i n k s t o o p e n a p p .
let safariURL = URL ( string : " https://www.threads.net/@ " + username ) !
UIApplication . shared . open ( safariURL , options : [ : ] )
}
@IBAction func followAltStoreMastodon ( )
{
2024-12-11 15:20:10 -05:00
self . openMastodon ( username : " @sidestoreio@fosstodon.org " )
2024-02-14 15:50:09 -06:00
}
@IBAction func followAltStoreThreads ( )
{
2024-12-11 15:20:10 -05:00
self . openThreads ( username : " sidestore.io " )
2024-02-14 15:50:09 -06:00
}
@IBAction func followAltStoreTwitter ( )
{
2024-12-11 15:20:10 -05:00
self . openTwitter ( username : " sidestoreio " )
2024-02-14 15:50:09 -06:00
}
@IBAction func followAltStoreGitHub ( )
{
2024-12-11 15:20:10 -05:00
let safariURL = URL ( string : " https://github.com/SideStore " ) !
2024-02-14 15:50:09 -06:00
UIApplication . shared . open ( safariURL , options : [ : ] )
}
2019-06-06 14:46:23 -07:00
}
2019-09-19 14:43:26 -07:00
private extension SettingsViewController
{
@objc func openPatreonSettings ( _ notification : Notification )
{
guard self . presentedViewController = = nil else { return }
UIView . performWithoutAnimation {
self . navigationController ? . popViewController ( animated : false )
self . performSegue ( withIdentifier : " showPatreon " , sender : nil )
}
}
2024-08-06 10:43:52 +09:00
@objc func openErrorLog ( _ : Notification ) {
guard self . presentedViewController = = nil else { return }
self . navigationController ? . popViewController ( animated : false )
2022-10-26 16:46:21 -05:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 0.2 ) {
2024-08-06 10:43:52 +09:00
self . performSegue ( withIdentifier : " showErrorLog " , sender : nil )
}
}
2025-04-20 08:48:50 +08:00
@objc func openExportCertificateConfirm ( _ notification : Notification )
{
func export ( )
{
guard let template = notification . userInfo ? [ AppDelegate . exportCertificateCallbackTemplateKey ] as ? String ,
template . contains ( " $(BASE64_CERT) " ) else {
let toastView = ToastView ( text : NSLocalizedString ( " No $(BASE64_CERT) placeholder found " , comment : " " ) , detailText : nil )
toastView . show ( in : self )
return
}
guard let data = Keychain . shared . signingCertificate ,
let password = Keychain . shared . signingCertificatePassword else {
let toastView = ToastView ( text : NSLocalizedString ( " Failed to find certificate or password " , comment : " " ) , detailText : nil )
toastView . show ( in : self )
return
}
let base64encodedCert = data . base64EncodedString ( )
var allowedQueryParamAndKey = NSCharacterSet . urlQueryAllowed
allowedQueryParamAndKey . remove ( charactersIn : " ;/?:@&=+$, " )
guard let encodedCert = base64encodedCert . addingPercentEncoding ( withAllowedCharacters : allowedQueryParamAndKey ) else {
let toastView = ToastView ( text : NSLocalizedString ( " Failed to encode certificate! " , comment : " " ) , detailText : nil )
toastView . show ( in : self )
return
}
var urlStr = template . replacingOccurrences ( of : " $(BASE64_CERT) " , with : encodedCert , options : . literal , range : nil )
urlStr = urlStr . replacingOccurrences ( of : " $(PASSWORD) " , with : password , options : . literal , range : nil )
print ( urlStr )
guard let callbackUrl = URL ( string : urlStr ) else {
let toastView = ToastView ( text : NSLocalizedString ( " Failed to initialize callback URL! " , comment : " " ) , detailText : nil )
toastView . show ( in : self )
return
}
UIApplication . shared . open ( callbackUrl )
}
let alertController = UIAlertController ( title : NSLocalizedString ( " Export Certificate " , comment : " " ) , message : NSLocalizedString ( " Do you want to export your certificate to an external app? That app will be able to sign apps using your certificate. " , comment : " " ) , preferredStyle : . alert )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Export " , comment : " " ) , style : . default ) { _ in export ( ) } )
alertController . addAction ( . cancel )
self . present ( alertController , animated : true , completion : nil )
}
2019-09-19 14:43:26 -07:00
}
2019-07-31 11:46:26 -07:00
extension SettingsViewController
2019-06-06 14:46:23 -07:00
{
2019-09-19 22:20:10 -07:00
override func numberOfSections ( in tableView : UITableView ) -> Int
{
var numberOfSections = super . numberOfSections ( in : tableView )
if ! UserDefaults . standard . isDebugModeEnabled
{
numberOfSections -= 1
}
return numberOfSections
}
2019-09-05 11:59:10 -07:00
override func tableView ( _ tableView : UITableView , numberOfRowsInSection section : Int ) -> Int
2019-06-06 14:46:23 -07:00
{
2019-09-05 11:59:10 -07:00
let section = Section . allCases [ section ]
switch section
{
2023-02-06 17:36:05 -06:00
case _ where isSectionHidden ( section ) : return 0
2019-09-05 11:59:10 -07:00
case . signIn : return ( self . activeTeam = = nil ) ? 1 : 0
case . account : return ( self . activeTeam = = nil ) ? 0 : 3
2020-09-08 16:44:36 -07:00
case . appRefresh : return AppRefreshRow . allCases . count
2019-09-05 11:59:10 -07:00
default : return super . tableView ( tableView , numberOfRowsInSection : section . rawValue )
}
}
2020-09-08 17:11:22 -07:00
override func tableView ( _ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell
{
let cell = super . tableView ( tableView , cellForRowAt : indexPath )
2024-08-06 10:43:52 +09:00
if #available ( iOS 14 , * ) { }
else if let cell = cell as ? InsetGroupTableViewCell ,
indexPath . section = = Section . appRefresh . rawValue ,
indexPath . row = = AppRefreshRow . backgroundRefresh . rawValue
{
// O n l y o n e r o w i s v i s i b l e p r e - i O S 1 4 .
cell . style = . single
}
2023-11-28 00:44:47 +09:00
if AppRefreshRow . AllCases ( ) . count = = 1
2020-09-08 17:11:22 -07:00
{
2023-11-28 00:44:47 +09:00
if let cell = cell as ? InsetGroupTableViewCell ,
indexPath . section = = Section . appRefresh . rawValue ,
indexPath . row = = AppRefreshRow . backgroundRefresh . rawValue
{
cell . style = . single
}
2020-09-08 17:11:22 -07:00
}
2024-12-12 20:03:31 +05:30
if let cell = cell as ? InsetGroupTableViewCell ,
indexPath . section = = Section . appRefresh . rawValue ,
indexPath . row = = AppRefreshRow . allCases . count - 1 // l a s t r o w
{
cell . setValue ( 3 , forKey : " style " )
}
2023-11-28 00:44:47 +09:00
2020-09-08 17:11:22 -07:00
return cell
}
2019-09-05 11:59:10 -07:00
override func tableView ( _ tableView : UITableView , viewForHeaderInSection section : Int ) -> UIView ?
{
let section = Section . allCases [ section ]
switch section
{
2023-02-06 17:36:05 -06:00
case _ where isSectionHidden ( section ) : return nil
2019-09-05 11:59:10 -07:00
case . signIn where self . activeTeam != nil : return nil
case . account where self . activeTeam = = nil : return nil
2025-06-12 12:47:33 +08:00
case . signIn , . account , . patreon , . display , . appRefresh , . techyThings , . credits , . advancedSettings , . signing , . diagnostics /* , . m a c D i r t y C o w */ :
2019-09-05 11:59:10 -07:00
let headerView = tableView . dequeueReusableHeaderFooterView ( withIdentifier : " HeaderFooterView " ) as ! SettingsHeaderFooterView
self . prepare ( headerView , for : section , isHeader : true )
return headerView
2020-09-08 16:44:36 -07:00
case . instructions : return nil
2019-09-05 11:59:10 -07:00
}
}
override func tableView ( _ tableView : UITableView , viewForFooterInSection section : Int ) -> UIView ?
{
let section = Section . allCases [ section ]
switch section
{
2023-02-06 17:36:05 -06:00
case _ where isSectionHidden ( section ) : return nil
2019-09-05 11:59:10 -07:00
case . signIn where self . activeTeam != nil : return nil
2024-12-12 17:51:59 +05:30
// c a s e . s i g n I n , . p a t r e o n , . d i s p l a y , . a p p R e f r e s h , . t e c h y T h i n g s , . m a c D i r t y C o w :
2025-06-12 12:47:33 +08:00
case . signIn , . patreon , . display , . appRefresh , . techyThings , . signing :
2019-09-05 11:59:10 -07:00
let footerView = tableView . dequeueReusableHeaderFooterView ( withIdentifier : " HeaderFooterView " ) as ! SettingsHeaderFooterView
self . prepare ( footerView , for : section , isHeader : false )
return footerView
2025-01-12 20:48:21 +05:30
case . account , . credits , . advancedSettings , . instructions , . diagnostics : return nil
2019-09-05 11:59:10 -07:00
}
2019-06-06 14:46:23 -07:00
}
2019-09-05 11:59:10 -07:00
override func tableView ( _ tableView : UITableView , heightForHeaderInSection section : Int ) -> CGFloat
{
let section = Section . allCases [ section ]
switch section
{
2023-02-06 17:36:05 -06:00
case _ where isSectionHidden ( section ) : return 1.0
2019-09-05 11:59:10 -07:00
case . signIn where self . activeTeam != nil : return 1.0
case . account where self . activeTeam = = nil : return 1.0
2025-01-12 20:48:21 +05:30
// c a s e . s i g n I n , . a c c o u n t , . p a t r e o n , . d i s p l a y , . a p p R e f r e s h , . t e c h y T h i n g s , . c r e d i t s , . m a c D i r t y C o w , . a d v a n c e d :
2025-06-12 12:47:33 +08:00
case . signIn , . account , . patreon , . display , . appRefresh , . techyThings , . credits , . advancedSettings , . signing , . diagnostics :
2019-09-05 11:59:10 -07:00
let height = self . preferredHeight ( for : self . prototypeHeaderFooterView , in : section , isHeader : true )
return height
2020-09-08 16:44:36 -07:00
case . instructions : return 0.0
2019-09-05 11:59:10 -07:00
}
}
override func tableView ( _ tableView : UITableView , heightForFooterInSection section : Int ) -> CGFloat
{
let section = Section . allCases [ section ]
switch section
{
2023-02-06 17:36:05 -06:00
case _ where isSectionHidden ( section ) : return 1.0
2019-09-05 11:59:10 -07:00
case . signIn where self . activeTeam != nil : return 1.0
2023-02-06 17:36:05 -06:00
case . account where self . activeTeam = = nil : return 1.0
2024-12-12 17:51:59 +05:30
// c a s e . s i g n I n , . p a t r e o n , . d i s p l a y , . a p p R e f r e s h , . t e c h y T h i n g s , . m a c D i r t y C o w :
2025-06-12 12:47:33 +08:00
case . signIn , . patreon , . display , . appRefresh , . techyThings , . signing , . diagnostics :
2019-09-05 11:59:10 -07:00
let height = self . preferredHeight ( for : self . prototypeHeaderFooterView , in : section , isHeader : false )
return height
2025-01-12 20:48:21 +05:30
case . account , . credits , . advancedSettings , . instructions : return 0.0
2019-09-05 11:59:10 -07:00
}
}
}
2019-06-06 14:46:23 -07:00
2019-09-05 11:59:10 -07:00
extension SettingsViewController
{
override func tableView ( _ tableView : UITableView , didSelectRowAt indexPath : IndexPath )
{
let section = Section . allCases [ indexPath . section ]
switch section
{
case . signIn : self . signIn ( )
2020-09-08 16:44:36 -07:00
case . appRefresh :
let row = AppRefreshRow . allCases [ indexPath . row ]
switch row
{
case . backgroundRefresh : break
2023-11-28 00:44:47 +09:00
case . noIdleTimeout : break
2024-10-11 01:12:22 -04:00
case . disableAppLimit : break
2020-09-08 16:44:36 -07:00
case . addToSiri :
2025-01-12 20:48:21 +05:30
// g u a r d # a v a i l a b l e ( i O S 1 4 , * ) e l s e { r e t u r n } / / o u r m i n d e p l o y m e n t i s i O S 1 5 n o w : ) s o c o m m e n t e d o u t
2020-09-08 16:44:36 -07:00
self . addRefreshAppsShortcut ( )
}
2023-02-07 16:11:39 -06:00
case . techyThings :
let row = TechyThingsRow . allCases [ indexPath . row ]
switch row
{
case . errorLog : break
case . clearCache : self . clearCache ( )
}
2023-02-07 16:11:39 -06:00
2019-09-07 15:34:07 -07:00
case . credits :
let row = CreditsRow . allCases [ indexPath . row ]
switch row
{
2024-11-21 12:12:47 -05:00
case . developer : self . openTwitter ( username : " sidestoreio " )
case . operations : self . openTwitter ( username : " sidestoreio " )
2022-12-11 12:03:45 -05:00
case . designer : self . openTwitter ( username : " lit_ritt " )
2019-09-21 13:57:18 -07:00
case . softwareLicenses : break
2019-09-07 15:34:07 -07:00
}
2024-02-14 17:54:20 -06:00
if let selectedIndexPath = self . tableView . indexPathForSelectedRow
{
self . tableView . deselectRow ( at : selectedIndexPath , animated : true )
}
2025-01-12 20:48:21 +05:30
case . advancedSettings :
let row = AdvancedSettingsRow . allCases [ indexPath . row ]
2019-09-12 13:23:21 -07:00
switch row
{
case . sendFeedback :
2024-08-12 21:54:34 -04:00
let alertController = UIAlertController ( title : " Send Feedback " , message : " Choose a method to send feedback: " , preferredStyle : . actionSheet )
// O p t i o n 1 : G i t H u b
alertController . addAction ( UIAlertAction ( title : " GitHub " , style : . default ) { _ in
if let githubURL = URL ( string : " https://github.com/SideStore/SideStore/issues " ) {
let safariViewController = SFSafariViewController ( url : githubURL )
safariViewController . preferredControlTintColor = . altPrimary
self . present ( safariViewController , animated : true , completion : nil )
2019-09-12 13:23:21 -07:00
}
2024-08-12 21:54:34 -04:00
} )
// O p t i o n 2 : D i s c o r d
alertController . addAction ( UIAlertAction ( title : " Discord " , style : . default ) { _ in
2025-03-15 06:30:03 -04:00
if let discordURL = URL ( string : " https://discord.gg/sidestore-949183273383395328 " ) {
2024-08-12 21:54:34 -04:00
let safariViewController = SFSafariViewController ( url : discordURL )
safariViewController . preferredControlTintColor = . altPrimary
self . present ( safariViewController , animated : true , completion : nil )
2019-09-12 13:23:21 -07:00
}
2024-08-12 21:54:34 -04:00
} )
// O p t i o n 3 : M a i l
2024-11-21 12:12:47 -05:00
alertController . addAction ( UIAlertAction ( title : " Send Email " , style : . default ) { _ in
if MFMailComposeViewController . canSendMail ( ) {
let mailViewController = MFMailComposeViewController ( )
mailViewController . mailComposeDelegate = self
mailViewController . setToRecipients ( [ " support@sidestore.io " ] )
2025-02-08 04:45:22 +05:30
// TODO: M A R K E T I N G _ V E R S I O N i s g o i n g t o b e s e t a n y w a y s s o t h i s n e e d s t o b e f i x e d f o r b e t a
2024-11-21 12:12:47 -05:00
if let version = Bundle . main . object ( forInfoDictionaryKey : " CFBundleShortVersionString " ) as ? String {
mailViewController . setSubject ( " SideStore Beta \( version ) Feedback " )
} else {
mailViewController . setSubject ( " SideStore Beta Feedback " )
}
self . present ( mailViewController , animated : true , completion : nil )
2025-01-02 20:05:16 +05:30
} else {
2024-11-21 12:12:47 -05:00
let toastView = ToastView ( text : NSLocalizedString ( " Cannot Send Mail " , comment : " " ) , detailText : nil )
toastView . show ( in : self )
2025-01-02 20:05:16 +05:30
}
} )
2024-08-12 21:54:34 -04:00
// C a n c e l a c t i o n
alertController . addAction ( UIAlertAction ( title : " Cancel " , style : . cancel , handler : nil ) )
// F o r i P a d : S e t t h e s o u r c e v i e w i f p r e s e n t i n g o n i P a d t o a v o i d c r a s h e s
if let popoverController = alertController . popoverPresentationController {
popoverController . sourceView = self . view
popoverController . sourceRect = self . view . bounds
2019-09-12 13:23:21 -07:00
}
2023-08-30 07:09:52 +00:00
2024-08-12 21:54:34 -04:00
// P r e s e n t t h e a c t i o n s h e e t
self . present ( alertController , animated : true , completion : nil )
2024-06-17 09:43:25 +10:00
case . refreshSideJITServer :
if #available ( iOS 17 , * ) {
2024-08-15 09:58:26 +10:00
2024-06-17 09:43:25 +10:00
let alertController = UIAlertController (
2024-08-15 09:58:26 +10:00
title : NSLocalizedString ( " SideJITServer " , comment : " " ) ,
message : NSLocalizedString ( " Settings for SideJITServer " , comment : " " ) ,
2024-06-17 09:43:25 +10:00
preferredStyle : UIAlertController . Style . actionSheet )
2024-08-15 09:58:26 +10:00
if UserDefaults . standard . sidejitenable {
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Disable " , comment : " " ) , style : . default ) { _ in
UserDefaults . standard . sidejitenable = false
} )
} else {
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Enable " , comment : " " ) , style : . default ) { _ in
UserDefaults . standard . sidejitenable = true
} )
}
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Server Address " , comment : " " ) , style : . default ) { _ in
let alertController1 = UIAlertController ( title : " SideJITServer Address " , message : " Please Enter the SideJITServer Address Below. (this is not needed if SideJITServer has already been detected) " , preferredStyle : . alert )
alertController1 . addTextField { textField in
textField . placeholder = " SideJITServer Address "
}
let cancelAction = UIAlertAction ( title : " Cancel " , style : . cancel , handler : nil )
alertController1 . addAction ( cancelAction )
let okAction = UIAlertAction ( title : " OK " , style : . default ) { _ in
if let text = alertController1 . textFields ? . first ? . text {
UserDefaults . standard . textInputSideJITServerurl = text
}
}
alertController1 . addAction ( okAction )
// P r e s e n t t h e a l e r t c o n t r o l l e r
self . present ( alertController1 , animated : true )
} )
2024-06-17 09:43:25 +10:00
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Refresh " , comment : " " ) , style : . destructive ) { _ in
if UserDefaults . standard . sidejitenable {
var SJSURL = " "
if ( UserDefaults . standard . textInputSideJITServerurl ? ? " " ) . isEmpty {
SJSURL = " http://sidejitserver._http._tcp.local:8080 "
} else {
SJSURL = UserDefaults . standard . textInputSideJITServerurl ? ? " "
2024-08-15 09:58:26 +10:00
}
2024-06-17 09:43:25 +10:00
let url = URL ( string : SJSURL + " /re/ " ) !
let task = URLSession . shared . dataTask ( with : url ) { ( data , response , error ) in
if let error = error {
print ( " Error: \( error ) " )
} else {
// D o n o t h i n g w i t h d a t a o r r e s p o n s e
}
}
task . resume ( )
}
} )
2024-08-15 09:58:26 +10:00
2024-06-17 09:43:25 +10:00
2024-08-15 09:58:26 +10:00
let cancelAction = UIAlertAction ( title : " Cancel " , style : . cancel , handler : nil )
alertController . addAction ( cancelAction )
2024-06-17 09:43:25 +10:00
// F i x c r a s h o n i P a d
alertController . popoverPresentationController ? . sourceView = self . tableView
alertController . popoverPresentationController ? . sourceRect = self . tableView . rectForRow ( at : indexPath )
self . present ( alertController , animated : true )
self . tableView . deselectRow ( at : indexPath , animated : true )
} else {
let alertController = UIAlertController (
title : NSLocalizedString ( " You are not on iOS 17+ This will not work " , comment : " " ) ,
message : NSLocalizedString ( " This is meant for 'SideJITServer' and it only works on iOS 17+ " , comment : " " ) ,
preferredStyle : UIAlertController . Style . actionSheet )
2023-08-30 04:47:36 +00:00
2024-06-17 09:43:25 +10:00
alertController . addAction ( . cancel )
// F i x c r a s h o n i P a d
alertController . popoverPresentationController ? . sourceView = self . tableView
alertController . popoverPresentationController ? . sourceRect = self . tableView . rectForRow ( at : indexPath )
self . present ( alertController , animated : true )
self . tableView . deselectRow ( at : indexPath , animated : true )
}
2023-01-09 15:15:31 +08:00
case . resetPairingFile :
2024-08-15 09:58:26 +10:00
2023-01-09 15:15:31 +08:00
let filename = " ALTPairingFile.mobiledevicepairing "
2024-08-15 09:58:26 +10:00
2023-01-09 15:15:31 +08:00
let fm = FileManager . default
2024-08-15 09:58:26 +10:00
2023-01-09 15:15:31 +08:00
let documentsPath = fm . documentsDirectory . appendingPathComponent ( " / \( filename ) " )
let alertController = UIAlertController (
title : NSLocalizedString ( " Are you sure to reset the pairing file? " , comment : " " ) ,
message : NSLocalizedString ( " You can reset the pairing file when you cannot sideload apps or enable JIT. You need to restart SideStore. " , comment : " " ) ,
preferredStyle : UIAlertController . Style . actionSheet )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Delete and Reset " , comment : " " ) , style : . destructive ) { _ in
if fm . fileExists ( atPath : documentsPath . path ) , let contents = try ? String ( contentsOf : documentsPath ) , ! contents . isEmpty {
2024-02-23 19:46:31 -05:00
UserDefaults . standard . isPairingReset = true
2023-01-09 15:15:31 +08:00
try ? fm . removeItem ( atPath : documentsPath . path )
NSLog ( " Pairing File Reseted " )
}
self . tableView . deselectRow ( at : indexPath , animated : true )
2024-02-23 19:46:31 -05:00
let dialogMessage = UIAlertController ( title : NSLocalizedString ( " Pairing File Reset " , comment : " " ) , message : NSLocalizedString ( " Please restart SideStore " , comment : " " ) , preferredStyle : . alert )
2023-01-09 15:15:31 +08:00
self . present ( dialogMessage , animated : true , completion : nil )
} )
alertController . addAction ( . cancel )
// F i x c r a s h o n i P a d
alertController . popoverPresentationController ? . sourceView = self . tableView
alertController . popoverPresentationController ? . sourceRect = self . tableView . rectForRow ( at : indexPath )
self . present ( alertController , animated : true )
self . tableView . deselectRow ( at : indexPath , animated : true )
2024-08-15 09:58:26 +10:00
2024-08-06 10:43:52 +09:00
case . anisetteServers :
2024-12-15 23:00:12 +05:30
func handleRefreshResult ( _ result : Result < Void , any Error > ) {
var message = " Servers list refreshed "
var details : String ? = nil
2024-12-16 20:23:42 +05:30
var duration : TimeInterval = 2.0
2024-12-15 23:00:12 +05:30
switch result {
case . success :
// N o a d d i t i o n a l a c t i o n n e e d e d , d e f a u l t m e s s a g e i s s u f f i c i e n t
break
case . failure ( let error ) :
message = " Failed to refresh servers list "
details = String ( describing : error )
duration = 4.0
}
let toast = ToastView ( text : message , detailText : details )
toast . preferredDuration = duration
toast . show ( in : self )
}
// I n s t a n t i a t e S w i f t U I V i e w i n s i d e U I H o s t i n g C o n t r o l l e r
let anisetteServersView = AnisetteServersView ( selected : UserDefaults . standard . menuAnisetteURL , errorCallback : {
ToastView ( text : " Cleared adi.pb! " , detailText : " You will need to log back into Apple ID in SideStore. " )
. show ( in : self )
} , refreshCallback : { result in
handleRefreshResult ( result )
} )
let anisetteServersController = UIHostingController ( rootView : anisetteServersView )
self . prepare ( for : UIStoryboardSegue ( identifier : " anisetteServers " , source : self , destination : anisetteServersController ) , sender : nil )
2025-01-12 20:48:21 +05:30
// c a s e . h i d d e n S e t t i n g s :
// / / C r e a t e t h e U R L t h a t d e e p l i n k s t o y o u r a p p ' s c u s t o m s e t t i n g s .
// i f l e t u r l = U R L ( s t r i n g : U I A p p l i c a t i o n . o p e n S e t t i n g s U R L S t r i n g ) {
// / / A s k t h e s y s t e m t o o p e n t h a t U R L .
// U I A p p l i c a t i o n . s h a r e d . o p e n ( u r l )
// } e l s e {
// E L O G ( " U I A p p l i c a t i o n . o p e n S e t t i n g s U R L S t r i n g i n v a l i d " )
// }
2025-02-09 17:28:24 +05:30
case . refreshAttempts , . betaUpdates , . betaTrack : break
2025-01-12 20:48:21 +05:30
}
2025-06-12 12:47:33 +08:00
case . signing :
let row = SigningSettingsRow . allCases [ indexPath . row ]
switch row {
2025-09-07 13:47:04 -04:00
case . exportAccount : showExportAccount ( )
case . importAccount :
Task {
let confUrl = await withUnsafeContinuation { c in
let importVc = UIDocumentPickerViewController ( forOpeningContentTypes : [ UTType ( filenameExtension : " sideconf " ) ! ] , asCopy : false )
ImportExport . documentPickerHandler = DocumentPickerHandler { url in
c . resume ( returning : url )
}
importVc . delegate = ImportExport . documentPickerHandler
self . present ( importVc , animated : true )
}
guard let confUrl else {
return
}
importAccountAtFile ( confUrl )
}
2025-06-12 12:47:33 +08:00
case . importCert :
2025-11-07 16:52:55 -05:00
let importVc = UIDocumentPickerViewController ( forOpeningContentTypes : [ UTType ( filenameExtension : " p12 " ) ! ] , asCopy : false )
ImportExport . documentPickerHandler = DocumentPickerHandler { url in
guard let url else {
return
}
importVc . delegate = ImportExport . documentPickerHandler
self . present ( importVc , animated : true )
_ = url . startAccessingSecurityScopedResource ( )
defer { url . stopAccessingSecurityScopedResource ( ) }
}
2025-06-12 12:47:33 +08:00
Task {
let certUrl = await withUnsafeContinuation { c in
let importVc = UIDocumentPickerViewController ( forOpeningContentTypes : [ UTType ( filenameExtension : " p12 " ) ! ] , asCopy : false )
ImportExport . documentPickerHandler = DocumentPickerHandler { url in
2025-11-07 16:52:55 -05:00
_ = url ? . startAccessingSecurityScopedResource ( )
defer { url ? . stopAccessingSecurityScopedResource ( ) }
2025-06-12 12:47:33 +08:00
c . resume ( returning : url )
}
importVc . delegate = ImportExport . documentPickerHandler
self . present ( importVc , animated : true )
}
guard let certUrl else {
return
}
let password = await withUnsafeContinuation { ( c : UnsafeContinuation < String ? , Never > ) in
let alertController = UIAlertController ( title : NSLocalizedString ( " Please enter the password for the certificate. " , comment : " " ) , message : nil , preferredStyle : . alert )
alertController . addTextField { ( textField ) in
textField . autocorrectionType = . no
textField . autocapitalizationType = . none
}
let submitAction = UIAlertAction ( title : NSLocalizedString ( " OK " , comment : " " ) , style : . default ) { ( action ) in
let textField = alertController . textFields ? . first
let code = textField ? . text ? ? " "
c . resume ( returning : code )
}
alertController . addAction ( submitAction )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Cancel " , comment : " " ) , style : . cancel ) { ( action ) in
c . resume ( returning : nil )
} )
self . present ( alertController , animated : true )
}
guard let password else {
return
}
2025-09-07 13:47:04 -04:00
_ = certUrl . startAccessingSecurityScopedResource ( )
2025-06-12 12:47:33 +08:00
defer {
certUrl . stopAccessingSecurityScopedResource ( )
}
let certData : Data
do {
certData = try Data ( contentsOf : certUrl )
} catch {
let toastView = ToastView ( text : NSLocalizedString ( " Failed to import certificate! " , comment : " " ) , detailText : error . localizedDescription )
toastView . show ( in : self )
return
}
guard let altCert = ALTCertificate ( p12Data : certData , password : password ) else {
let toastView = ToastView ( text : NSLocalizedString ( " Failed to import certificate! " , comment : " " ) , detailText : " Failed to create ALTCertificate. Check if the password is correct. " )
toastView . show ( in : self )
return
}
Keychain . shared . signingCertificate = altCert . encryptedP12Data ( withPassword : " " ) !
let toastView = ToastView ( text : NSLocalizedString ( " Certificate imported successfully! " , comment : " " ) , detailText : nil )
toastView . show ( in : self )
}
case . exportCert :
Task {
guard let certData = Keychain . shared . signingCertificate else {
let toastView = ToastView ( text : NSLocalizedString ( " Failed to export certificate! " , comment : " " ) , detailText : " Certificate not found. " )
toastView . show ( in : self )
return
}
let password = await withUnsafeContinuation { ( c : UnsafeContinuation < String ? , Never > ) in
let alertController = UIAlertController ( title : NSLocalizedString ( " Please enter the password for the certificate. " , comment : " " ) , message : nil , preferredStyle : . alert )
alertController . addTextField { ( textField ) in
textField . autocorrectionType = . no
textField . autocapitalizationType = . none
}
let submitAction = UIAlertAction ( title : NSLocalizedString ( " OK " , comment : " " ) , style : . default ) { ( action ) in
let textField = alertController . textFields ? . first
let code = textField ? . text ? ? " "
c . resume ( returning : code )
}
alertController . addAction ( submitAction )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Cancel " , comment : " " ) , style : . cancel ) { ( action ) in
c . resume ( returning : nil )
} )
self . present ( alertController , animated : true )
}
guard let password else {
return
}
guard let altCert = ALTCertificate ( p12Data : certData , password : nil ) else {
let toastView = ToastView ( text : NSLocalizedString ( " Failed to export certificate! " , comment : " " ) , detailText : " Failed to create ALTCertificate. Check if the password is correct. " )
toastView . show ( in : self )
return
}
guard let newCertData = altCert . encryptedP12Data ( withPassword : password ) else {
let toastView = ToastView ( text : NSLocalizedString ( " Failed to export certificate! " , comment : " " ) , detailText : " Failed to encrypt ALTCertificate. " )
toastView . show ( in : self )
return
}
let newCertTmpPath = FileManager . default . temporaryDirectory . appendingPathComponent ( " SideStoreSigningCertificate.p12 " )
do {
try newCertData . write ( to : newCertTmpPath )
} catch {
let toastView = ToastView ( text : NSLocalizedString ( " Failed to export certificate! " , comment : " " ) , detailText : error . localizedDescription )
toastView . show ( in : self )
return
}
let exportVC = UIDocumentPickerViewController ( forExporting : [ newCertTmpPath ] , asCopy : false )
self . present ( exportVC , animated : true )
}
}
2025-01-12 20:48:21 +05:30
case . diagnostics :
let row = DiagnosticsRow . allCases [ indexPath . row ]
switch row {
2025-02-08 04:45:22 +05:30
case . deleteDatabase :
if ! Self . deleteDBInProgress {
Self . deleteDBInProgress = true
_ = DatabaseManager . deleteDatabase ( )
exit ( 0 ) // e x i t a p p i m m e d i a t e l y t o p r e v e n t d b u s a g e a n d c r a s h e s
}
case . exportDatabase :
2025-01-02 20:05:16 +05:30
// d o n o t a c c e p t s i m u l a t e n o u s e x p o r t r e q u e s t s
2025-02-08 04:45:22 +05:30
if ! Self . exportDBInProgress {
Self . exportDBInProgress = true
2025-01-02 20:05:16 +05:30
Task {
var toastView : ToastView ?
do {
let exportedURL = try await CoreDataHelper . exportCoreDataStore ( )
print ( " exportSqliteDB: ExportedURL: \( exportedURL ) " )
toastView = ToastView ( text : " Export Successful " , detailText : nil )
} catch {
print ( " exportSqliteDB: \( error ) " )
toastView = ToastView ( error : error )
}
// s h o w t o a s t t o u s e r a b o u t t h e r e s u l t
DispatchQueue . main . async {
toastView ? . show ( in : self )
}
// u p d a t e t h a t w o r k h a s f i n i s h e d
2025-02-08 04:45:22 +05:30
Self . exportDBInProgress = false
2025-01-02 20:05:16 +05:30
}
}
2025-01-14 07:23:23 +05:30
case . operationsLoggingControl :
2025-02-16 21:32:24 +05:30
2025-01-14 07:23:23 +05:30
// I n s t a n t i a t e S w i f t U I V i e w i n s i d e U I H o s t i n g C o n t r o l l e r
let operationsLoggingControlView = OperationsLoggingControlView ( )
let operationsLoggingController = UIHostingController ( rootView : operationsLoggingControlView )
let segue = UIStoryboardSegue ( identifier : " operationsLoggingControl " , source : self , destination : operationsLoggingController )
self . present ( segue . destination , animated : true , completion : nil )
2025-02-08 04:45:22 +05:30
case . responseCaching , . exportResignedApp , . verboseOperationsLogging , . minimuxerConsoleLogging , . recreateDatabase : break
2019-09-12 13:23:21 -07:00
}
2025-01-12 20:48:21 +05:30
2024-12-12 17:51:59 +05:30
// c a s e . a c c o u n t , . p a t r e o n , . d i s p l a y , . i n s t r u c t i o n s , . m a c D i r t y C o w : b r e a k
case . account , . patreon , . display , . instructions : break
2019-09-05 11:59:10 -07:00
}
2025-02-16 21:32:24 +05:30
// d e s e l e c t t h e r o w b e f o r e r e t u r n i n g ( s o t h a t i t d o e s n ' t l o o k l i k e s t u c k s e l e c t e d )
tableView . deselectRow ( at : indexPath , animated : true )
2019-09-05 11:59:10 -07:00
}
}
2019-09-12 13:23:21 -07:00
extension SettingsViewController : MFMailComposeViewControllerDelegate
{
func mailComposeController ( _ controller : MFMailComposeViewController , didFinishWith result : MFMailComposeResult , error : Error ? )
{
if let error = error
{
2020-03-30 14:07:18 -07:00
let toastView = ToastView ( error : error )
toastView . show ( in : self )
2019-09-12 13:23:21 -07:00
}
controller . dismiss ( animated : true , completion : nil )
}
}
2019-09-19 22:20:10 -07:00
extension SettingsViewController : UIGestureRecognizerDelegate
{
func gestureRecognizer ( _ gestureRecognizer : UIGestureRecognizer , shouldRecognizeSimultaneouslyWith otherGestureRecognizer : UIGestureRecognizer ) -> Bool
{
return true
}
}
2020-09-08 16:44:36 -07:00
extension SettingsViewController : INUIAddVoiceShortcutViewControllerDelegate
{
func addVoiceShortcutViewController ( _ controller : INUIAddVoiceShortcutViewController , didFinishWith voiceShortcut : INVoiceShortcut ? , error : Error ? )
{
if let indexPath = self . tableView . indexPathForSelectedRow
{
self . tableView . deselectRow ( at : indexPath , animated : true )
}
controller . dismiss ( animated : true , completion : nil )
guard let error = error else { return }
let toastView = ToastView ( error : error )
toastView . show ( in : self )
}
func addVoiceShortcutViewControllerDidCancel ( _ controller : INUIAddVoiceShortcutViewController )
{
if let indexPath = self . tableView . indexPathForSelectedRow
{
self . tableView . deselectRow ( at : indexPath , animated : true )
}
controller . dismiss ( animated : true , completion : nil )
}
}