Compare commits

...

2185 Commits
1.0 ... develop

Author SHA1 Message Date
mahee96
bc617b476d updated submodule 2026-06-14 05:46:20 +05:30
mahee96
d12d9b7117 updated submodule 2026-06-14 05:43:52 +05:30
mahee96
466b8fa658 fix CI - deploy account for apps-v2 2026-06-14 05:39:34 +05:30
mahee96
46eae1cb9f fix CI - deploy account for apps-v2 2026-06-14 05:14:55 +05:30
mahee96
7eb0da332d updated roxas submodule ref to SideStore's 2026-06-14 05:06:48 +05:30
mahee96
710dea8b28 roxas was out of sync 2026-06-14 05:01:55 +05:30
mahee96
51233911cc roxas was otut of sync 2026-06-14 03:59:59 +05:30
mahee96
b13d0abca9 - xcode 27 support (bumped ios, tvOS minSDK to 15) 2026-06-14 03:52:39 +05:30
mahee96
239fc7c2b7 0.6.4 tag marker 2026-05-05 13:09:20 +05:30
mahee96
b06bed49c2 - Fix: corrected device IP in vpn configuration screen + corrected altsign package to use master-branch 2026-05-05 12:15:45 +05:30
mahee96
96ae7f6048 Merge branch 'develop' 2026-05-05 12:02:25 +05:30
nythepegasus
34b7ed859b chore: bump to 0.6.4 [noci]
Signed-off-by: nythepegasus <mobile@nythepegas.us>
2026-05-05 00:49:16 -04:00
Huge_Black
dbaa4292ec fix: fix crash in source detail view 2026-04-12 14:24:25 +08:00
Huge_Black
a7cd3f7608 fix: fix widget issue once again. PLEASE INCLUDE ViewApp.intentdefinition OTHERWISE WIDGET WILL NOT WORK 2026-04-12 14:24:11 +08:00
CelloSerenity
525ea84138 feat: LC Source Update (#1253)
Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>
2026-04-10 23:31:21 -04:00
nythepegasus
87a48897d0 feat: extend range of exploit check
this is a huge hack but for now and what this is used for elsewhere

Signed-off-by: nythepegasus <mobile@nythepegas.us>
2026-04-09 20:35:59 -04:00
mahee96
8cb2d96833 change build to use .xcodeproj instead of workspace 2026-04-04 01:43:15 -07:00
mahee96
7028ed928d remove xcworkspace - obsolete now 2026-04-04 01:37:20 -07:00
Huge_Black
acb7e99329 update minimuxer to fix build issue 2026-04-04 11:56:32 +08:00
Huge_Black
d255650855 Fix build issue by upgrading xcode? 2026-04-04 11:43:37 +08:00
Huge_Black
ba12e99d8c Update minimuxer to support RPPairing & add back NSAttributedString again 2026-04-04 11:27:05 +08:00
mahee96
0e16450f2f updated em_proxy submodule 2026-04-01 15:56:41 -07:00
mahee96
12c4357114 cleanup: removed Jailbreak stuff, PatchAppOperation etc. since no longer relevant 2026-04-01 15:44:34 -07:00
mahee96
08d5363f89 SendAppOperation: added missing import 2026-04-01 14:24:19 -07:00
mahee96
ab03ef68e7 Merge branch 'develop' into merges
# Conflicts:
#	AltStore.xcodeproj/project.pbxproj
#	AltStore/Operations/InstallAppOperation.swift
#	AltStore/Operations/SendAppOperation.swift
#	Dependencies/minimuxer
#	SideStore/MinimuxerWrapper.swift
2026-04-01 14:21:38 -07:00
Huge_Black
700defef21 Fix AltBackup.ipa is not included
Added a symlink in AltSotore/Resources/AltBackup.ipa that points to build/AltBackup.ipa

It seems Xcode reads all contents in AltSotore/Resources before ipa-altbackup runs, so AltBackup.ipa is missing in the first build. Adding a symlink will cause Xcode to always include that file
2026-03-21 15:30:49 +08:00
Huge_Black
f0ef0b0db4 Merge pull request #1218 from LiveContainer/develop-lc
Fix widget not working & Only run turn off data shortcut when minimuxer is not ready and below 26.4
2026-03-21 12:08:32 +08:00
Huge_Black
8c635989cb Only run turn off data shortcut when minimuxer is not ready and below 26.4 2026-03-21 12:01:16 +08:00
Huge_Black
2ee430d758 Fix widget not working 2026-03-21 11:58:20 +08:00
suprstarrd
095269f765 feat: add Mona to Trusted Sources (#1210)
* feat: add Mona to Trusted Sources

Signed-off-by: suprstarrd <business@suprstarrd.com>
2026-03-17 12:28:56 -04:00
mahee96
444a25c087 vpn-configuration: fix - properly report active status 2026-03-03 13:24:48 +05:30
mahee96
32b3210247 vpn-configuration: fix typo in sentence 2026-03-03 12:29:22 +05:30
mahee96
5cce14a4c3 vpn-configuration: updated to reflect actual implementation 2026-03-03 12:27:12 +05:30
mahee96
47779499db minimuxer: gutted discovery mechanism for finding the fake deviceIP from uTun routes coz it is impossible to walk subnet and kernel route table doesn't tell active associations but just possible routes attempted 2026-03-03 12:20:47 +05:30
mahee96
5e437fe04e vpn-configuration: removed UI updating state despite bindTunnelConfig() driving it just behind 2026-03-03 10:57:12 +05:30
mahee96
7ecca56166 minimuxer: fix some stale state issues during VPN up/down cycles or changing config 2026-03-03 10:49:36 +05:30
mahee96
61ae14dc4a minimuxer: added back file headers info about authors 2026-03-03 09:59:48 +05:30
mahee96
24feb2055a DeviceEndpoint: fix all minimuxer woes by properly handling usbmuxd signals for listdevices and listen 2026-03-03 09:20:39 +05:30
mahee96
3ed4cf5eb5 MinimuxerWrapper: remove spam logs 2026-03-03 04:33:07 +05:30
mahee96
eee34304c6 Heartbeat: improved logging intent 2026-03-03 04:32:46 +05:30
mahee96
1a199f7c54 IfaceScanner: fix some state update issues 2026-03-03 04:02:07 +05:30
mahee96
b1cbb5203e IfaceScanner: used the user specified IP to directly test if device is available there instead of assuming it to be under the subnet of tunnel coz it is fake endpoint interceptable by the tunnel's included routes 2026-03-03 03:23:00 +05:30
mahee96
e6a2a540f4 minimuxer: added binding for the cached tunnel config values so that application layer can provide and listen to updates. 2026-03-03 02:57:39 +05:30
mahee96
5144f9894b vpn-configuration: added new swiftUI view to settings to view discovered vpn settings and provide an override for deviceIP (if required) by user. 2026-03-03 02:54:58 +05:30
mahee96
a9ca0c4ce7 minimuxer: fix usbmuxd socket error handling - restart on first error and dont wait for 5 lol 2026-03-02 17:05:38 +05:30
mahee96
515bf2ef43 minimuxer: match API name 2026-03-02 16:57:56 +05:30
mahee96
c3d4d895d7 minimuxer: usbmuxer was not handling socket kill or death properly when app was backgrounded and came into foreground 2026-03-02 16:57:37 +05:30
mahee96
b1aa7d03a8 minimuxer: IfaceScanner - updated to skim thru kernel routing tables and find the included routes of our uTun 2026-03-02 14:59:08 +05:30
mahee96
150fe32d41 minimuxer: gut checks for allowRange/whitelist since we control it on the VPN side 2026-03-02 10:14:21 +05:30
mahee96
81cbbb058c pbxproj - switch to sidestore repos for minimuxer 2026-03-02 08:56:45 +05:30
mahee96
2bdf59da21 minimuxer: added empty header dirs required by xcframework 2026-03-02 08:28:25 +05:30
mahee96
23fc3ce51e minimuxer: removed target dir from exlcudes since it is in .gitignore 2026-03-02 08:25:37 +05:30
mahee96
92908e5092 minimuxer: added the xcframework libs which was missed before 2026-03-02 08:22:49 +05:30
mahee96
2e7a566598 pbxproj - removed local references and used github references for altsign and minimuxer spm packages 2026-03-02 08:11:36 +05:30
mahee96
acf3236105 cellular-refresh: disable shortcuts spawning until this feature is complete since it interferes with testing for now. 2026-03-02 08:03:41 +05:30
mahee96
5fcd8f88fd minimuxer: allow specific network address for sidestore tunnels 2026-03-02 07:58:21 +05:30
mahee96
6327a80517 bug-fix: be on right thread when doing UI work or accessing UI references/UIApplication 2026-03-02 07:34:35 +05:30
mahee96
4cc54178d3 minimuxer: added dynamic lookup of peer for utun(VPN) when not in P2P which is required for sidestore 2026-03-02 07:34:07 +05:30
mahee96
1306d863db minimuxer: added dynamic lookup of peer for utun(VPN) when not in P2P which is required for sidestore 2026-03-02 07:33:51 +05:30
mahee96
177a9afe15 bug-fix: ensure main thread when using UIApplication.shared 2026-03-02 04:54:40 +05:30
mahee96
13291e34d9 MinimuxerWrapper - for now lets use hardcoded addresses as before - so commented out dynamic discovery 2026-03-02 04:54:20 +05:30
mahee96
467ba5c66e minimuxer: install - added device connection check before requesting afc services 2026-03-02 04:53:37 +05:30
mahee96
517d281b08 minimuxer: muxer works now - but has heartbeat issue due to getDevice() failing sometimes, will investigate next. 2026-03-02 03:24:45 +05:30
mahee96
518f6cf632 pbxproj: restore required slowly - added all missing source files (.h, .c, .cpp) for libimobiledevice and added missing entries under products for libimobiledevice, libem_proxy-swift 2026-03-01 18:00:37 +05:30
mahee96
b2a304d257 pbxproj: restore required slowly - added back libimobiledevice compilation sources (.h, .c, .cpp) 2026-03-01 16:49:03 +05:30
mahee96
86b5c5e789 submodules: updated to latest 2026-03-01 16:31:45 +05:30
mahee96
dd5260837a pbxproj: restore required slowly - cleanup + added em_proxy target libs properly 2026-03-01 16:31:45 +05:30
mahee96
bfe51d4cf1 em_proxy: updated submodule to latest 2026-03-01 16:13:05 +05:30
mahee96
dfbccc29cd pbxproj: restore required slowly - streamline em_proxy dependencies 2026-03-01 16:08:38 +05:30
mahee96
714d49b4ac em_proxy: consolidate em_proxy stuff into its own repo (moved em_proxy.xcodeproj, fetch-prebuilt.sh into its repo) 2026-03-01 16:08:07 +05:30
mahee96
eb199a4d56 pbxproj: restore required slowly - added back xcode auto-discovered roxas related framework, product and xcodeproj (xcode won't let me live in peace otherwise) - now total pbxproj lines = 2.5K 2026-03-01 15:41:35 +05:30
mahee96
c6438ca190 pbxproj: restore required slowly - added back xcode auto-discovered framework, xcodeproj (xcode is persistent on adding this back into pbxproj even if I ignore or remove it). 2026-03-01 15:38:00 +05:30
mahee96
06bafdaddc pbxproj: restore required slowly - added Dependencies/** references after careful vetting by GROUP reference instead of FOLDER reference in xcode 2026-03-01 15:36:22 +05:30
mahee96
b7ccefc332 pbxproj: restore required slowly - added Dependencies bare reference 2026-03-01 15:23:01 +05:30
mahee96
1442d4a9ab pbxproj: restore required slowly - removed Dependencies as a folder and will add back as group coz xcode scans the frameworks and xcodeproj files again and again each time due to it being folder type reference 2026-03-01 15:04:06 +05:30
mahee96
2f31628da8 minimuxer: check-in package.resolved 2026-03-01 14:57:35 +05:30
mahee96
d758bd559b minimuxer: reverted to using pre-compiled(and checked-in) RustBridge.xcframework which will be accessible only by MinimuxerBridge which will expose it to minimuxer swift layer under Sources/*.swift . 2026-03-01 14:55:17 +05:30
mahee96
1752396657 pbxproj: restore required slowly - added em_proxy swift source and static lib reference 2026-03-01 14:55:12 +05:30
mahee96
ff9f530411 pbxproj: cleanup - removed obsolete xcscheme 2026-03-01 14:54:09 +05:30
mahee96
12987544fe pbxproj: restore required slowly - added depdencies dir reference 2026-03-01 13:11:39 +05:30
mahee96
0de5c0be29 pbxproj: massive cleanup - removed duplicate and stale entries - bring down from 10K to 1.5K lines 2026-03-01 13:06:27 +05:30
ny
60d5923447 fix: revert 26.4 fix partially to fix everywhere 2026-02-28 18:03:35 -05:00
mahee96
2f38bf4062 workspace: added minimuxer SPM package 2026-03-01 01:49:12 +05:30
mahee96
651d43d8d4 MinimuxerWrapper: cleanup 2026-03-01 01:48:45 +05:30
mahee96
3d7ff564f2 MinimuxerWrapper: streamline the logging 2026-03-01 01:21:06 +05:30
mahee96
44c22a5f6b minimuxer: use newly exposed Swift wrappers in MinimuxerWrapper.swift 2026-03-01 01:20:47 +05:30
mahee96
685fcc7641 minimuxer: removed old minimuxer binaries fetch from the fetch script 2026-03-01 01:20:47 +05:30
mahee96
cd8bef9820 pbxproj: removed minimuxer as a static lib and switched to SPM based 2026-03-01 01:18:18 +05:30
mahee96
9d6e76f3ca minimuxer: removed old xcodeproj 2026-03-01 01:18:18 +05:30
mahee96
1c8d110367 minimuxer: removed old xcodeproj 2026-03-01 01:18:18 +05:30
mahee96
ac24144174 minimuxer: switch to build tool plugin for rust build with SPM target 2026-03-01 01:18:18 +05:30
mahee96
a738a37219 minimuxer: use zip-foundation instead of process for zip/unzip 2026-02-28 15:08:17 +05:30
mahee96
ca0867adca minimuxer: added few more files that were missed 2026-02-28 14:56:17 +05:30
mahee96
e9812b0d66 minimuxer: added Package.swift which was missed 2026-02-28 14:46:51 +05:30
mahee96
8f61fcc592 minimuxer: updated wrapper to use spm based reference of swift-minimuxer 2026-02-28 14:23:18 +05:30
mahee96
4c741f8161 minimuxer: full rewrite in swift from rust 2026-02-28 14:22:55 +05:30
ny
b3dcccfd18 fix: 26.4 patch, add correct dest
- update minimuxer
2026-02-27 22:21:41 -05:00
mahee96
dddb250137 IfManager: added String representation for readability 2026-02-28 06:45:12 +05:30
mahee96
5c92a1ae82 pbxproj: was missing references to IfManager.swift 2026-02-28 04:41:33 +05:30
mahee96
82ccff4b5b minimuxer: added @nythepegasus's patch as-is 2026-02-28 04:24:03 +05:30
mahee96
298d20d0b0 minimuxer: added @nythepegasus's patch as-is 2026-02-28 04:23:58 +05:30
mahee96
6d3bb9301a minimuxer: updated submodule to latest 2026-02-28 03:52:35 +05:30
mahee96
c55e34e76a ALTSign cleanup 2026-02-25 08:16:42 +05:30
mahee96
f40096d455 ALTSign cleanup 2026-02-25 08:15:43 +05:30
mahee96
77acf14239 CI: add what xcode is suggesting (xcode noise from auto create schemes) 2026-02-25 08:15:31 +05:30
mahee96
abe34a7183 CI: changelog in md was incorrect in release notes 2026-02-25 08:08:46 +05:30
mahee96
01804fb649 CI: fixes for author 2026-02-25 07:46:54 +05:30
mahee96
0e1d05fa4c CI: multiple fixes 2026-02-25 07:33:55 +05:30
mahee96
0269d038f5 CI: remove harcoded repo name and use incoming 2026-02-25 03:30:57 +05:30
mahee96
9e2c363d11 CI: create tag if required 2026-02-25 03:18:31 +05:30
mahee96
2cebeb5037 CI: handle zipping irrespective of encryption is possible or not 2026-02-25 03:14:50 +05:30
mahee96
c0a71b7808 fix compilation issue after recent merge 2026-02-25 03:00:34 +05:30
mahee96
0047cd6cbe Merge branch 'cell-staging' into develop 2026-02-25 02:50:13 +05:30
mahee96
89e44b0202 ci - decouple source_metadata.json generation from deploy 2026-02-25 02:41:58 +05:30
mahee96
1d1e4ddca9 ci - do not encrypt build logs if password unavailable (forks CI friendly) 2026-02-25 01:45:23 +05:30
mahee96
957c69427d Merge branch 'cell-shortcut' into cell-staging
# Conflicts:
#	AltStore/Operations/SendAppOperation.swift
2026-02-25 01:20:56 +05:30
mahee96
7c05f0bedf cleanup - xcode brought these new entries for schemes 2026-02-25 01:18:09 +05:30
mahee96
ad2f18f744 Merge branch 'staging' into develop 2026-02-25 00:50:28 +05:30
mahee96
4ab3003084 ci: fixes for yml 2026-02-25 00:42:02 +05:30
mahee96
6bbf77a789 ci: fixes for yml 2026-02-25 00:40:09 +05:30
mahee96
a743d984a6 ci: fixes for yml 2026-02-25 00:30:07 +05:30
mahee96
82a67a6d32 ci: use runner number as build number 2026-02-25 00:25:11 +05:30
mahee96
dbe6dffdc0 ci: fixes in workflow.py 2026-02-25 00:10:40 +05:30
mahee96
f2b8e238ed ci: fix - tag was not pushed 2026-02-25 00:08:41 +05:30
mahee96
4d1f932c2f ci: fix - version number was inconsistent across deployment of beta channels 2026-02-25 00:04:42 +05:30
mahee96
83c4a5ebb5 ci: updated stable workflow as per workflow.py and other workflows 2026-02-24 22:07:36 +05:30
mahee96
ca3d8c7d16 ci: updated PR workflow to reflect latest 2026-02-24 21:15:28 +05:30
mahee96
90e539e6b2 ci: use proper messaging for upstream tag 2026-02-24 20:51:25 +05:30
mahee96
da3b7ff022 ci: cleanup - removed obsolete stuffs 2026-02-24 20:43:35 +05:30
mahee96
37d3256d10 ci: moved cache maintenance script into obsolete 2026-02-24 20:43:03 +05:30
Huge_Black
e40a6c9ba6 Merge pull request #1183 from LiveContainer/develop
Fix issues profile/afc and use main profile issue
2026-02-24 23:07:11 +08:00
mahee96
25bb785aa7 ci: fix typo in brew command 2026-02-24 20:35:50 +05:30
mahee96
6ebd037f64 ci: removed caching for ldid and xcbeautify coz they take much more time to restore than install 2026-02-24 20:32:02 +05:30
mahee96
6aeed08d43 ci: added some names to few steps 2026-02-24 20:27:46 +05:30
mahee96
1c1d75cbd6 ci: added back the nightly schedule checking for new commits and only then build 2026-02-24 20:23:12 +05:30
mahee96
e0f0e7c1b8 ci: improve speed by caching brew install step and reading project settings from dumped xcodebuild -showProjectSettings instead of invoking each time. 2026-02-24 18:54:41 +05:30
mahee96
6f7ff618b7 ci: fix indentation in release notes 2026-02-24 18:20:27 +05:30
mahee96
2b8b007a3b ci: fix indentation in release notes 2026-02-24 18:19:54 +05:30
Huge_Black
801fd2c283 bug-fix: fix crash when viewing app ids 2026-02-24 16:08:15 +08:00
Huge_Black
a25c3d03d5 bug-fix: fix profile not installed 2026-02-24 16:08:15 +08:00
Huge_Black
b3c04bfc83 fix build issue 2026-02-24 16:08:15 +08:00
Huge_Black
affa4f027b bug-fix: fix useMainProfile not saved / not set during initialization
SideStore is killed by iOS before CoreData commit changes to db.

Detect if the app is installed with useMainProfile by checking if applicationIdentifier matches
2026-02-24 16:08:15 +08:00
Huge_Black
515243f8af bug-fix: Fix crash when installing apps 2026-02-24 16:08:15 +08:00
mahee96
05a5081d1b ci: fix indentation in release notes 2026-02-24 13:20:14 +05:30
mahee96
b17882a18b staging: added some redundant files to gitignore 2026-02-24 13:14:40 +05:30
mahee96
02c78b8422 staging: prepare new branch for alpha release channels and high velocity development 2026-02-24 13:04:44 +05:30
mahee96
3044bd1e84 ci: more fixes 2026-02-24 12:42:55 +05:30
mahee96
90f8b92e45 ci: more fixes 2026-02-24 12:25:12 +05:30
mahee96
ef21097907 ci: more fixes 2026-02-24 09:13:09 +05:30
mahee96
4f6f1ff60e ci: more fixes 2026-02-24 08:42:52 +05:30
mahee96
f3a1d5e0c6 CI: improve more ci worflow 2026-02-24 08:19:56 +05:30
mahee96
10fb99198d CI: improve more ci worflow 2026-02-24 07:41:06 +05:30
mahee96
bb233cc459 CI: improve more ci worflow 2026-02-24 07:23:20 +05:30
mahee96
8b6c807618 CI: improve more ci worflow 2026-02-24 07:21:14 +05:30
mahee96
f947ac0ec1 CI: improve more ci worflow 2026-02-24 07:12:56 +05:30
mahee96
2c23197500 CI: improve more ci worflow 2026-02-24 07:00:17 +05:30
mahee96
ef5297695e CI: improve more ci worflow 2026-02-24 06:22:03 +05:30
mahee96
cec86092e6 CI: improve more ci worflow 2026-02-24 05:47:38 +05:30
mahee96
9b96d8e6d8 CI: improve more ci worflow 2026-02-24 05:27:15 +05:30
mahee96
8a3bd79de8 CI: improve more ci worflow 2026-02-24 03:58:47 +05:30
mahee96
d5e06125f4 CI: improve more ci worflow 2026-02-24 03:53:26 +05:30
mahee96
d3957e2bbf CI: improve more ci worflow 2026-02-24 03:47:15 +05:30
mahee96
bdb1d66b70 CI: improve more ci worflow 2026-02-24 03:33:26 +05:30
mahee96
b850747ae3 CI: improve more ci worflow 2026-02-24 03:28:46 +05:30
mahee96
8c1c0c69d7 CI: improve more ci worflow 2026-02-24 03:21:19 +05:30
mahee96
60ea582a72 CI: improve more ci worflow 2026-02-24 03:16:26 +05:30
mahee96
cfe61972a5 CI: improve more ci worflow 2026-02-24 03:13:55 +05:30
mahee96
7ac38774fe CI: improve more ci worflow 2026-02-24 02:59:08 +05:30
mahee96
5d628b2af7 CI: improve more ci worflow 2026-02-24 02:53:51 +05:30
mahee96
ba35ff928d CI: improve more ci worflow 2026-02-24 02:43:02 +05:30
mahee96
14cb48555b CI: improve more ci worflow 2026-02-24 02:40:34 +05:30
mahee96
8bafbe7220 altsign updated to latest 2026-02-24 02:40:30 +05:30
mahee96
16e4f91756 CI: improve more ci worflow 2026-02-24 02:29:13 +05:30
mahee96
ac8af3857f CI: improve more ci worflow 2026-02-24 02:25:50 +05:30
mahee96
acea56c91c CI: improve more ci worflow 2026-02-24 02:22:45 +05:30
mahee96
a8b4aea727 CI: improve more ci worflow 2026-02-24 02:20:10 +05:30
mahee96
fd712a02cc CI: improve more ci worflow 2026-02-24 02:14:13 +05:30
mahee96
4810fbe84a re added openSSL from new path 2026-02-24 02:14:10 +05:30
mahee96
e4e4b196c2 updated altsign to use xcframework for openSSL which was causing huge download of 1.2 GB each time 2026-02-24 02:13:48 +05:30
mahee96
a4b0895ca0 CI: improve more ci worflow 2026-02-24 01:54:41 +05:30
mahee96
1211fab953 CI: improve more ci worflow 2026-02-24 01:54:05 +05:30
mahee96
a477a195fd CI: improve more ci worflow 2026-02-24 01:51:35 +05:30
mahee96
f9a94b78b1 CI: improve more ci worflow 2026-02-24 01:41:36 +05:30
mahee96
fd85fb0a25 CI: improve more ci worflow 2026-02-24 01:36:33 +05:30
mahee96
2204b7944f CI: improve more ci worflow 2026-02-24 01:26:13 +05:30
mahee96
30cb9fd7fd CI: improve more ci worflow 2026-02-24 01:20:37 +05:30
mahee96
77313761db CI: improve more ci worflow 2026-02-24 01:10:38 +05:30
mahee96
699300f2d6 CI: improve more ci worflow 2026-02-24 00:59:05 +05:30
mahee96
a845907cc3 CI: improve more ci worflow 2026-02-24 00:41:33 +05:30
mahee96
e6987dc638 CI: full rewrite - moved logic into ci.py and kept workflow scripts mostly dummy 2026-02-23 20:21:59 +05:30
mahee96
4fa67c3c76 try adding libfragmentzip again with explicit dependency 2026-02-23 16:26:56 +05:30
mahee96
06dd407035 try adding libfragmentzip again with explicit dependency 2026-02-23 14:42:37 +05:30
mahee96
1a2858c0e0 dependencies: fix few more issues with project reference 2026-02-23 14:28:53 +05:30
mahee96
09f88c2623 dependencies: fix few more issues with project reference 2026-02-23 12:51:36 +05:30
mahee96
4fceb5c101 dependencies: covert most back to folders except few that are required by build for reference 2026-02-23 12:45:24 +05:30
mahee96
742f79261a dependencies: fix missing header by adding to output list 2026-02-23 12:30:19 +05:30
mahee96
18fbce1e27 dependencies: use group format for folders to see if that fixes chaining issue 2026-02-23 12:24:07 +05:30
mahee96
2c61ad09d1 dependencies: use group format for folders to see if that fixes chaining issue 2026-02-23 12:12:51 +05:30
mahee96
7a5837f8c5 wrappers: keep imports them private to file scope for encapsulation 2026-02-23 11:53:17 +05:30
mahee96
69464b43b3 minimuxer: fix some wrapper methods 2026-02-23 11:45:15 +05:30
mahee96
25c1073f5d build: fix some more path issues 2026-02-23 11:43:46 +05:30
mahee96
4a7d514ece minimuxer: fix some wrapper methods 2026-02-23 11:43:23 +05:30
mahee96
dc8e836eb2 minimuxer: use wrapper methods 2026-02-23 11:30:11 +05:30
mahee96
4f847cb398 schemes: do not auto create 2026-02-23 11:29:47 +05:30
mahee96
6910debc0f fix: attempt to fix the dependency chaining issue 2026-02-23 10:29:02 +05:30
mahee96
b407bb7249 cleanup: removed more stuffs - datastructure tests and ui tests which we don't actively use anyways...they can still be used by running manually if required. 2026-02-23 02:12:33 +05:30
mahee96
c4231445ab libcurl: added back as dependency 2026-02-23 00:55:08 +05:30
mahee96
b994ab4460 xcschemes: disabled unecessary ones 2026-02-22 23:47:00 +05:30
mahee96
bce57d7659 bug-fix: Cargo project build in em_proxy, minimuxer weren't declaring the artifacts as outputs causing them to be missed being generated during manual clean and re-run 2026-02-22 23:46:21 +05:30
mahee96
6e3bd8d0d0 cleanup: removed more unused commented out code 2026-02-22 22:58:11 +05:30
mahee96
844d83f32c submodule: updated to latest 2026-02-22 21:46:46 +05:30
mahee96
b130dbf219 submodule: updated to latest 2026-02-22 21:29:05 +05:30
mahee96
fa4ee4d798 refactor: streamline the repo again and removed unused files from altstore 2026-02-22 21:27:31 +05:30
mahee96
9623221d09 debloat: converted xcode groups to xcode folder + removed unused files 2026-02-22 16:17:01 +05:30
mahee96
6e2ae440bd debloat: removed more patreon stuff carried over from altstore 2.0 2026-02-22 15:38:23 +05:30
mahee96
07102d2771 cleanup: removed more unused commented out code 2026-02-22 15:19:00 +05:30
mahee96
651d35f6ee debloat: removed more patreon stuff carried over from altstore 2.0 2026-02-22 15:18:51 +05:30
mahee96
f2436f7d88 cleanup: removed more unused commented out code 2026-02-22 15:01:18 +05:30
mahee96
3a0873a9e4 debloat: removed more patreon stuff carried over from altstore 2.0 2026-02-22 15:00:25 +05:30
mahee96
19a67eb04a debloat: remove patreon stuff carried over from altstore 2.0...not required by sidestore in-app since sidestore manages in web + remove old tests from altstore 2026-02-22 14:54:01 +05:30
mahee96
138fe7d967 cleanup: removed analytics stuff - we can add a decent analytics management later if we need 2026-02-22 13:57:43 +05:30
mahee96
a5ac11e76c bug-fix: should be checking for iconName and not imageName anymore 2026-02-22 11:27:05 +05:30
mahee96
ed8d0771a4 icons: use 256x256 for preview imageset since 75x75 was too poor 2026-02-22 11:19:50 +05:30
mahee96
2fac2478d6 icons: fix incorrect name entry 2026-02-22 11:01:15 +05:30
mahee96
0fbc8bd8ca bug-fix: emp for wireguard requires an enum entry for the indexPath calc 2026-02-22 10:59:04 +05:30
mahee96
e773233770 icons: change app icon screen was not showing icon previews - added tiny 75x75 imageset assets since iOS 18+ cannot use AppIconSet for UIImage(named:) 2026-02-22 10:58:32 +05:30
mahee96
8b39b314ed CI: skip info on upstream branch for deploy message in nightly since it is already the last beta before stable 2026-02-22 07:39:24 +05:30
mahee96
51ade6fcb2 CI: disabled test tracks completely by commenting out 2026-02-22 07:29:56 +05:30
mahee96
771891ef43 settings: split out beta testing section so that users can enable it confidently as they see 2026-02-22 07:25:07 +05:30
mahee96
15021413ac CI: fixed the heredoc parsing issues when single quotes are present in the notes by using a local function in shell script 2026-02-22 06:22:41 +05:30
mahee96
3c1d3e8972 cleanup: improved messaging for appID customization 2026-02-22 05:31:26 +05:30
mahee96
027c6d8e92 bug-fix: customizeAppId key should be used from UserDefaults.standard instead of UserDefaults.shared 2026-02-22 05:31:10 +05:30
mahee96
dd87408afa bug-fix: main-profile processing should account for the effective AppID which is in context.bundleIdentifier 2026-02-22 05:30:39 +05:30
mahee96
a74de17be2 bug-fix: switch toggle handler shouldn't disable the switch itself 2026-02-22 05:29:55 +05:30
mahee96
4e2d507124 cleanup: updated strings and variables to reflect appExBundleID 2026-02-22 04:11:58 +05:30
mahee96
ebf3af881a bug-fix: appEx bundleID when bundleID is overridden, wasn't handled correctly in PR #1158 2026-02-22 04:11:25 +05:30
Magesh K
b8743cbe86 Merge pull request #1158 from SideStore/appId-customization
Feature: AppId customization during manual IPA install via "My Apps" screen
2026-02-20 05:36:24 +05:30
Magesh K
a6985b0b23 Merge branch 'develop' into appId-customization 2026-02-20 05:35:26 +05:30
ny
603ef0c376 fix: remove pointless error toasts 2026-02-17 12:44:25 -05:00
mahee96
4e6628d65d settings: added new switch to allow enabling appId customization (which will be shown during install) 2026-02-07 07:59:39 +05:30
peroka-net
265f0083e8 feat: Add UntitledCharts to Trusted Sources (#1160)
* Add new trusted app 'com.sbuga.retrosekai'
2026-02-04 11:05:06 -05:00
mahee96
b9341804b8 fix: cached app after download was not respecting overriden appId/bundleId 2026-02-02 11:28:48 +05:30
mahee96
cdbca88aab feature: added a prompt before installing to customize appId 2026-02-02 08:44:49 +05:30
mahee96
fbbde964e8 cleanup - removed unused commented code (already validated in 0.6.2 that refresh path doesnt need reinstalls since it only resigns the app) 2026-02-02 08:41:42 +05:30
mahee96
96b33c698f added gitignore for minimuxer submodule 2026-02-02 08:39:50 +05:30
mahee96
0521a3d8f0 Fix: base bundleID for AltBackup debug config was incorrect 2026-02-02 08:39:32 +05:30
CelloSerenity
18153f26b6 Update trustedapps.json 2026-01-26 23:14:00 -05:00
CelloSerenity
8e732031c0 Update trustedapps.json
Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>
2026-01-26 14:16:05 -05:00
Huge_Black
40b431a59f fix typo & log token login error 2026-01-24 23:43:50 -05:00
Huge_Black
7ead3c77dd Authentication rework, cache session, save and login with adsid and xcodeToken to alleviate GSA connection issue in some regions. Fix a timezone issue when extracting anisette data 2026-01-24 23:43:50 -05:00
basketshoe
b8cf188df7 Update pairing file help link in alert (#1147)
Signed-off-by: basketshoe <54729294+basketshoe@users.noreply.github.com>
2026-01-10 14:33:52 -05:00
CelloSerenity
d18b68207b Update trusted apps identifiers and source URLs
Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>
2025-12-17 23:21:03 -05:00
CelloSerenity
4b33a919c2 Sources: Update/Remove Sources from Trusted Sources (#1131)
* Add new trusted apps and remove outdated ones

Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>

* Update trustedapps.json

* Update trustedapps.json

Co-authored-by: Stern <xsternent@gmail.com>
Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>

* Update trustedapps.json

---------

Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>
Co-authored-by: Stern <xsternent@gmail.com>
2025-12-16 20:03:50 -05:00
CelloSerenity
ae600b1576 LocalDevVPN (#1126) 2025-12-04 21:40:39 -05:00
Stossy11
dcc93b4b07 Merge pull request #1078 from CelloSerenity/csdev
Update error codes
2025-11-25 08:06:18 +11:00
CelloSerenity
27206c4493 Update error messages to reference iloader
Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>
2025-11-20 16:50:45 -07:00
CelloSerenity
08da6c57ba Merge branch 'SideStore:develop' into csdev 2025-11-20 16:49:02 -07:00
mahee96
50013a13df entitlements: code_sign_entitlements declaration was missing in AltStore.debug/release modes causing AppGroups access failure (nil in getPreviousBackupURL) when long pressing app entry in MyApps list
Default entitlements are to come from AltStoreFree.entitlements, for paid entitlements one can use CodeSigning.xcconfig to override it to point to AltStore.entitlements instead.
2025-11-16 04:41:26 +05:30
mahee96
7e4012143e Fix: removed forced entry of libem_proxy-sim.a from depenencies which would otherwise cause build failure when targeting iOS device 2025-11-16 03:24:56 +05:30
mahee96
f0dd1d1393 EMP-toggle: UI corner radius fix 2025-11-16 03:07:29 +05:30
mahee96
a89b955f13 EMP: added back EMP entry/exit points as it was in 0.6.1 but with option to enable/disable it via settings 2025-11-16 03:01:29 +05:30
mahee96
48bec9dc4d EMP: added back em_proxy as it was in 0.6.1 2025-11-16 03:00:43 +05:30
mahee96
7375fee882 CI: deploy - added filtering to upload only available files 2025-11-16 01:25:01 +05:30
mahee96
db970d6398 CI: delete obsoleted scripts 2025-11-16 01:24:24 +05:30
mahee96
b1782bd535 CI: refine upload list in deploy job based on test enablement 2025-11-16 01:02:42 +05:30
mahee96
05299848d3 CI: remove pod related steps 2025-11-16 00:51:02 +05:30
mahee96
d31c758890 Pods: removed pods entirely and replace pod dependencies with swift packages 2025-11-16 00:46:51 +05:30
Pratik Lohani
86315428a8 Fix expired Discord link in README.md (#1101) 2025-11-14 14:24:35 -05:00
CelloSerenity
4008e679e2 Merge branch 'SideStore:develop' into csdev 2025-11-08 19:54:10 -07:00
CelloSerenity
4b14618970 Update README.md 2025-11-08 19:49:50 -07:00
CelloSerenity
10d3155e28 Fix version numbering 2025-11-08 19:23:59 -07:00
ny
9fa629d1d2 fix: copied too much (*sigh* UIKit) 2025-11-07 17:17:08 -05:00
ny
36b96b012c fix: Account exporting/importing 2025-11-07 16:54:39 -05:00
nythepegasus
3385b4a4af feat: Updated deprecated Pojav identifier to new Amethyst repo
Signed-off-by: nythepegasus <mobile@nythepegas.us>
2025-10-22 17:04:05 -04:00
mahee96
19764eba43 UITests: disable spotlight dismiss which is causing failures in test 2025-10-16 00:16:11 +05:30
mahee96
7b138a9837 UITests: added more idling waits since we removed(mocked) quiescent idling wait from testing framework at runtime. 2025-10-15 23:33:48 +05:30
mahee96
1d504c839c UITests: fixes for iOS 26 compatibility 2025-10-15 22:52:31 +05:30
mahee96
22674a4f2c CI: updated to macOS 26 + xcode 26 for runners 2025-10-15 20:32:42 +05:30
mahee96
0ed0ca7981 iOS26: added support for iOS 26 deployment target + CI (fixed layout issues, added splash screen, fixed nav title insets). 2025-10-15 20:22:35 +05:30
CelloSerenity
ca1d035d4c Update OperationError.swift
Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>
2025-10-10 15:34:12 -06:00
CelloSerenity
d263d165c7 Enhance error messages for UDID and SideJIT issues
Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>
2025-10-10 14:56:54 -06:00
CelloSerenity
bd2520b11c Update error messages for clarity and detail
Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>
2025-10-10 14:53:39 -06:00
ny
514254c8b3 feat: add the ability to import/export account data 2025-09-07 13:47:04 -04:00
mahee96
a9badab4f9 - [ConsoleLogger]: Fix writing large data by chunking + stale handle capture 2025-08-06 02:03:18 +05:30
mahee96
29e09ee76b - [ConsoleLogger]: Fix race conditions during shutdown and possible crashes by writing to closed stream handler 2025-07-30 23:35:24 +05:30
CelloSerenity
3553215316 Small fixes (#1020)
* here

* Update PatreonComponents.swift

Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>

* Update ErrorLogViewController.swift

Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>

* Update README.md

Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>

* Update trustedapps.json

Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>

* Revert "Update trustedapps.json"

This reverts commit 10a38895ea.

* Update Build.xcconfig

Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>

---------

Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>
2025-07-18 21:24:55 -04:00
Huge_Black
7300ca53c7 Use main profile when refreshing (#1013) 2025-06-22 08:31:13 -04:00
Huge_Black
2ed6d1e028 Add option for using main bundle's profile for app extensions, update AltSign & libmd (#1012) 2025-06-22 04:13:15 -04:00
neo
d68df01718 Merge pull request #1011 from CelloSerenity/develop 2025-06-14 17:54:05 -04:00
CelloSerenity
f9afb7cb20 Update LaunchViewController.swift
Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>
2025-06-14 14:04:20 -06:00
Huge_Black
61dcd44740 Add import/export certificate feature (#1008) 2025-06-12 00:47:33 -04:00
Stern
3320199c16 Merge pull request #995 from CelloSerenity/develop
Update OperationError.swift for idevice_pair
2025-05-24 21:06:24 -04:00
CelloSerenity
ec0a2ec340 Update OperationError.swift
Co-authored-by: Stern <xsternent@gmail.com>
Signed-off-by: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com>
2025-05-24 19:04:25 -06:00
CelloSerenity
3b9b245d5c Update OperationError.swift 2025-05-24 18:52:56 -06:00
polymo1
4a66cb1f23 Merge pull request #987 from owoellen/patch-1
Change front-facing references to AltStore
2025-05-19 18:35:07 -04:00
ellen!
74f9173883 Update LockScreenWidget.swift
Signed-off-by: ellen! <78277148+owoellen@users.noreply.github.com>
2025-05-19 22:45:50 +01:00
ellen!
3312aec8dd Update Authentication.storyboard
Signed-off-by: ellen! <78277148+owoellen@users.noreply.github.com>
2025-05-19 22:43:45 +01:00
Stossy11
28e98119da Feat: Add Pairing File Export URL callback and replace Wireguard with StosVPN (#962)
* Add Pairing File Export URL callback and replace Wireguard with StosVPN

* Fix Data()

* Add Switching Tab

* Fix Pairing Callback Template and fix spelling mistake

* Add Observer for openExportPairingFileConfirm

* Add Logging and fix certificate callback

* add func to Scene Delegate

* Fix ToastView and Logging

* Remove ?

* Fix Logging

* Call AppDelegate instead of SceneDelegate

* Fix Scene Delegate

* Set back SceneDelegate

* Add Logging

* Fix error

* Fix Case Sensitive

* Remove openExportPairingFileConfirm and fix AppDelegate.exportPairingFileNotification

* Remove Alert

* Remove Unnecessary code

* rename export() to exportPairingFile()
2025-04-24 01:48:29 -04:00
Huge_Black
11871c4d41 Add export certificate feature (#959) 2025-04-19 20:48:50 -04:00
Jackson Coxson
c115e552bb Bump minimuxer 2025-04-16 15:16:24 -04:00
Jackson Coxson
279b57e4ac Update SideStore for new minimuxer 2025-04-16 15:16:24 -04:00
Magesh K
8d411af842 Merge pull request #935 from mahee96/develop-minimuxer
Disable minimuxer ready check which shows NO WIFI/VPN error since stosVPN can work on cellular too
2025-04-09 00:03:04 -07:00
Magesh K
bc2c4cb2ac Merge pull request #945 from SideStore/develop-alpha
StosVPN-integration: Removed em_proxy and EmotionalDamage module in favour of stosVPN
2025-04-08 23:39:58 -07:00
Magesh K
0c8bd8243c Merge branch 'develop' into develop-alpha 2025-04-08 23:38:52 -07:00
mahee96
c41ccd7496 - bump version to 0.6.2 for next release cycle 2025-04-08 23:37:25 -07:00
mahee96
7353981d20 em_proxy: removed em_proxy submodule from git 2025-04-08 23:08:57 -07:00
mahee96
9b3243d6c2 - stosVPN-integration: Removed em_proxy and EmotionalDamage module in favour of stosVPN 2025-04-08 23:03:49 -07:00
mahee96
ad3155efd7 Merge branch 'develop' into develop-alpha 2025-04-08 22:46:26 -07:00
Magesh K
239f8436a6 Merge pull request #944 from SideStore/develop
Merge develop into main for 0.6.1 release
2025-04-08 22:03:54 -07:00
spidy123222
9167fb7407 Add Exit Shortcut 2025-04-08 15:19:33 -07:00
spidy123222
6a7d4dd3d2 Rewrite SendAppOperation execution to allow to wait for shortcut execution. 2025-04-08 15:19:33 -07:00
spidy123222
d6e8c37347 Move to SendAppOperation 2025-04-08 15:19:33 -07:00
spidy123222
4896ca50a3 move it behind pendiungunitcount 60 2025-04-08 15:19:33 -07:00
spidy123222
10019b3edc Move attempt to a higher Stage. 2025-04-08 15:19:33 -07:00
spidy123222
48d9fcfc28 Attempt a million 2025-04-08 15:19:33 -07:00
Spidy123222
78b3b36047 Add files via upload
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2025-04-08 15:19:33 -07:00
Spidy123222
447f7814b6 remove from install apps
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2025-04-08 15:19:33 -07:00
Spidy123222
4714d34beb Hopefully fix problem
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2025-04-08 15:19:33 -07:00
Spidy123222
a51a144052 fix error for open link
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2025-04-08 15:19:33 -07:00
Spidy123222
65efa4fdd6 test open URL
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2025-04-08 15:19:33 -07:00
polymo1
d80c84b49b Merge pull request #940 from Br0des/develop
Added StosVPN to the EM Proxy part.
2025-04-05 22:38:53 -04:00
Brodes
a0e02acfea Added StosVPN to the EM Proxy part.
yeah i'm petty >:3

Signed-off-by: Brodes <144500576+Br0des@users.noreply.github.com>
2025-04-05 20:13:07 -06:00
mahee96
7d9109119a - minimuxer-ready: disabled minimuxer ready check which shows NO WIFI/VPN error since stosVPN can work on cellular too. 2025-04-03 00:49:07 -07:00
Joseph LaFreniere
e258dbcf88 [Skip Ci] Fix typo "levaraging" -> "leveraging" (#926)
Signed-off-by: Joseph LaFreniere <git@lafreniere.xyz>
2025-03-31 15:12:25 -07:00
Magesh K
29f5e2139c Merge pull request #920 from mahee96/develop
Migration: Fixes for migration issues when migrating from 0.5.9 to 0.6.0
2025-03-25 20:37:16 -07:00
mahee96
fb7393033d - migration-fix: more reinforcements for 0.6.0 to 0.6.1 2025-03-24 01:32:01 -07:00
mahee96
8a7028aae0 - [Fix]: migrations fix for coredata from v11(0.5.9) to v17_1(0.6.1) and v17(0.6.0 to v17_1(0.6.1) 2025-03-24 00:19:05 -07:00
mahee96
7eb6d104a5 - [Fix]: migrations fix for coredata from v11(0.5.9) to v17_1(0.6.1) and v17(0.6.0 to v17_1(0.6.1) 2025-03-23 21:44:06 -07:00
mahee96
58fa3ee744 - [WIP]: migrations fix for coredata from v11(0.5.9) to v17_1(0.6.1) and v17(0.6.0 to v17_1(0.6.1) 2025-03-23 12:09:58 -07:00
mahee96
779abadae7 - fix: attempt to fix "app no longer available" issues due to fetch profiles not updating appFeatures and appGroups (possible regression from PR#846) 2025-03-23 12:00:31 -07:00
mahee96
9dbe969812 - [WIP]: migrations fix for coredata from v11(0.5.9) to v17_1(0.6.1) and v17(0.6.0 to v17_1(0.6.1) 2025-03-23 11:59:01 -07:00
mahee96
50c4e8dbf4 Revert "- Fixed: attempt migrations fix from 0.5.10 to 0.6.0"
This reverts commit 3ac52eb8d3.
2025-03-23 11:57:16 -07:00
polymo1
25438cd7dd Merge pull request #915 from neoarz/patch-3
Update SettingsViewController.swift
2025-03-15 10:13:42 -04:00
neoarz
b58f67b4db Update SettingsViewController.swift
Signed-off-by: neoarz <164915254+neoarz@users.noreply.github.com>
2025-03-15 06:30:03 -04:00
mahee96
7cee66c636 - fix: attempt to fix "app no longer available" issues due to fetch profiles not updating appFeatures and appGroups (possible regression from PR#846) 2025-03-15 01:58:08 +05:30
Zero King
eaa321669f fix: typo in hasUpdate comparison
Signed-off-by: Zero King <l2dy@icloud.com>
2025-03-13 03:29:02 +05:30
mahee96
3ac52eb8d3 - Fixed: attempt migrations fix from 0.5.10 to 0.6.0 2025-03-11 04:44:23 +05:30
Zero King
b14e9eabf4 fix: typo in hasUpdate comparison
Signed-off-by: Zero King <l2dy@icloud.com>
2025-03-08 22:43:20 +05:30
Magesh K
51a77c948b Merge pull request #900 from l2dy/patch-1
fix: typo in hasUpdate comparison
2025-03-04 03:33:32 +05:30
Zero King
72d1b97cee fix: typo in hasUpdate comparison
Signed-off-by: Zero King <l2dy@icloud.com>
2025-03-02 01:47:04 +08:00
mahee96
dd3088e77c - CI: updated stable.yml for recent fixes 2025-03-01 01:30:15 +05:30
mahee96
f7db9a1c02 - CI: updated stable.yml for recent fixes 2025-02-28 23:41:31 +05:30
Magesh K
a35c082717 Merge pull request #897 from SideStore/markdown-for-update-description
Feature: Render in-app update description as (full) markdown
2025-02-28 05:22:37 +05:30
mahee96
a5fa5dd65b - Feature: Markdown view integration complete (if issues arrise can fix it asap) 2025-02-28 05:09:37 +05:30
mahee96
a639617b38 - Fix: CollapsingMarkdownView was not reloading properly during layoutSubviews phase 2025-02-28 02:28:34 +05:30
mahee96
5def10c196 - Fixes: disabled animations for the CollapsingMarkdownView when expanding/collapsing due to visual glitches 2025-02-28 01:02:41 +05:30
mahee96
c451bcb34f - Feature: Added markdown rendering for in-app update description field 2025-02-27 23:39:03 +05:30
mahee96
3ed5b773ca - CI: serialize on whole workflow instead of individual jobs 2025-02-27 05:37:35 +05:30
mahee96
4c603cd9a0 - CI: serialize on whole workflow instead of individual jobs 2025-02-27 05:36:41 +05:30
mahee96
6f646adbd0 - CI: fix release notes updater for invalid revision as input 2025-02-27 05:23:20 +05:30
mahee96
773194b645 - CI: fix release notes updater for invalid revision as input 2025-02-27 05:22:11 +05:30
mahee96
ea81f03c98 - CI: enable build on push for nightly temporarily 2025-02-27 05:03:42 +05:30
mahee96
3b3fa1f72d - CI: fix: deploy errors 2025-02-27 05:01:27 +05:30
mahee96
31406951cf -CI: fix: deploy must run if tests were sccessful or skipped 2025-02-27 04:57:34 +05:30
mahee96
ace1226468 - Makefile: build-tests need destination param same as run-tests 2025-02-27 04:44:35 +05:30
mahee96
3d3c671015 - CI: Try composite build 2025-02-27 04:30:45 +05:30
mahee96
739bee1cdb - UITests: Fix issue with UITests bundleID causing test failures in CI 2025-02-27 02:50:16 +05:30
Magesh K
fae8b02bb3 - CI: replaced irgaly/xcode-cache with gh-actions-cache
- CI: serialization bug fix - reverted concurrency lock to workflow level instead of job level which was causing issues due to incorrect cache at expected jobs
- CI: Boot simulator in async instead of doing in a blocking step
- CI: integrate publishing release notes
- CI: moved posting to beta-build-num repo into build-job instead of deploy coz we want to roll the beta-build-num for each success of build-job even if the workflow failed.
2025-02-27 02:49:20 +05:30
Magesh K
77fc7a1c9a Merge pull request #894 from SideStore/coredata-migration-fix
Version 0.6.0: CoreData Migrations fix including xcdatamodels and xcmappingmodels
2025-02-26 12:57:22 +05:30
Magesh K
f1660434e2 - Migrations-Fix: fixed the migrations by creating custom migration policy for Source and StoreApp to support sourceID renaming and ReleaseTracks integration respectively, removed intermediate models and xcmappingmodels since we jumped directly from altstore 1.6.3 to 2.0.x 2025-02-26 12:43:26 +05:30
Magesh K
057e17390e Merge pull request #888 from SideStore/develop-alpha
Feature: Bulk sources-add in sources screen with capability to stage them before persisting into database
2025-02-26 11:47:46 +05:30
Magesh K
2beaa8345c - Bug-Fix: use normal keyboardType instead of url keyboardType for input field in add-source view 2025-02-24 20:09:14 +05:30
Magesh K
5f1551f1cc - CI: Optimization: Split tests-build from tests-run so that tests-run failure doesn't invalidate last build in cache 2025-02-24 19:42:07 +05:30
Magesh K
cf44e9f7a9 - CI: Fix: VERSION_IPA was not using $marketing_version 2025-02-23 23:16:18 +05:30
Magesh K
08df673962 - UITests: Fix: exclude pojavlauncher from testing since it is unreliable and fetch fails 2025-02-23 22:16:40 +05:30
Magesh K
900c858a9f - CI: Split sequential steps into build, deploy, publish jobs and use concurrency for build and test 2025-02-23 21:17:41 +05:30
Magesh K
597fbcd257 - UITests: Added more tests for testing repeatability 2025-02-23 18:04:57 +05:30
Magesh K
8606f3280b - UITests: Fixes for setup/teardown and added repeatability tests 2025-02-23 15:12:55 +05:30
Magesh K
b1267d6779 - CI: Makefile and CI bug-fixes 2025-02-22 20:30:56 +05:30
Magesh K
86491af213 - UITest-Fix: Add proper delays to wait for the UI Elements to appear on screen 2025-02-22 20:15:02 +05:30
Magesh K
a0f6504d16 - UITest-Fix: use home-screen(springboard) to delete the app coz sometime spotlight indexing is not up-to-date and this causes issues. 2025-02-22 19:55:05 +05:30
Magesh K
728df41117 - CI: improvements: dispatch simulator boot up in background and other fixes 2025-02-22 19:11:13 +05:30
Magesh K
dc9b8e4245 - .gitignore: Added more files to gitignore 2025-02-22 18:39:07 +05:30
Magesh K
48fa80f7c0 - CI: Fixes to make build re-useable 2025-02-22 18:35:49 +05:30
Magesh K
30574dfb2c - UITests: Fixed some issues with CI simulator delays 2025-02-22 18:35:23 +05:30
Magesh K
df7c7dca80 - CI: Improve build log capture in case of errors 2025-02-22 17:12:12 +05:30
Magesh K
fd570fc60f - CI: Included tests for CI builds 2025-02-22 02:37:30 +05:30
Magesh K
3fbf6b692e - Makefile: Added test and clean targets, fixed override params bug when empty 2025-02-22 01:44:24 +05:30
Magesh K
fb520e9a84 - Bug-Fix: bulk added source previews were shown out of order 2025-02-22 00:25:49 +05:30
Magesh K
502180f85f - UnitTests: Moved DS unit tests into their own target 2025-02-21 19:24:40 +05:30
Magesh K
63bb0a11cb - Bug-Fix: Use LinkedHashMap instead of swift standard dict which preserves insertion order 2025-02-21 19:09:07 +05:30
Magesh K
fe47f09708 - Feature: Implement Bulk add for Sources 2025-02-21 19:06:17 +05:30
Magesh K
39756fe45c - UnitTests: Added unitttests for new datastructures - LinkedHashMap and TreeMap 2025-02-21 19:04:40 +05:30
Magesh K
a706872b2c - DataStructures: Implemented LinkedHashMap and TreeMap (RB Tree) in swift 2025-02-21 19:03:28 +05:30
Magesh K
2ab7e7c7dd - UITests: Added new - testBulkAddRecommendedSources() 2025-02-21 18:01:40 +05:30
Magesh K
497527437e - UITests: Setup for UI Tests 2025-02-21 12:20:18 +05:30
Magesh K
a0f720a271 - Settings-Fix: deselect rows after user selection and before performing actions 2025-02-16 21:32:24 +05:30
Magesh K
77a94a537f - Fix: Use "EXPIRED" marker in MyApps screen instead of -ve interval 2025-02-16 20:48:06 +05:30
Magesh K
0258ae2071 - Renamed beta track to nightly 2025-02-16 20:28:57 +05:30
Magesh K
64ec405957 - ReleaseTracks: Added in-app ReleaseTracks switching support 2025-02-10 13:53:31 +05:30
Magesh K
c99f6c9864 - Minor fixes and cleanup 2025-02-09 17:31:00 +05:30
Magesh K
0ecac9a450 - Fixed spacing in App View title 2025-02-08 20:28:29 +05:30
Magesh K
421242f44e - Make unused AppPermissions attribs optional 2025-02-08 14:24:00 +05:30
Magesh K
a52fbadf37 - CI: fix typo 2025-02-08 14:24:00 +05:30
Magesh K
18efea1d49 - CI: updated nightly to print new commits when running on schedule 2025-02-08 13:50:45 +05:30
Magesh K
dc9014b959 - Store: Reverted localized version for Store and version to be independent 2025-02-08 13:38:37 +05:30
Magesh K
5bd201fe2c - Beta-Builds: Updated bundleID suffix with beta build to match source 2025-02-08 12:17:45 +05:30
Magesh K
29311b20e8 - Settings: Fixed last row corners not rounded 2025-02-08 11:56:42 +05:30
Magesh K
fd30ee8a2c - CI update for nightly 2025-02-08 11:44:40 +05:30
Magesh K
94e41498a2 - Multiple fixes and CI setup 2025-02-08 11:36:55 +05:30
Magesh K
a251f9894c Merge pull request #871 from neoarz/develop
Fix Repository App Previews for “countdown”
2025-02-03 05:11:07 +05:30
neoarz
6b8bed1484 Update StoreApp.swift 2025-02-02 17:52:57 -05:00
neoarz
cf117ca232 Merge branch 'SideStore:develop' into develop 2025-02-02 17:34:42 -05:00
Magesh K
7ae85afb99 [DisableIdleTimeout]: Fix: account for concurrency 2025-02-02 04:38:46 +05:30
Magesh K
915a9cb0c1 [DisableIdleTimeout]: Fix: moved setting isIdleTimerDisabled = false to AppManager.finish() 2025-02-02 04:18:36 +05:30
Magesh K
f76a8a035c [AppIDs-View]: updated header section size 2025-02-02 03:55:11 +05:30
Magesh K
80cd779506 [cleanup]: Sources tab add source storyboard 2025-02-02 03:55:11 +05:30
Magesh K
8f22cfb9ec [cleanup]: Disable patrons related stuff as it is non-functional 2025-01-29 02:48:12 +05:30
Magesh K
a486ce9515 [Fix]: VerifyError message was missing version info for mismatched versions 2025-01-29 02:38:27 +05:30
Magesh K
21eb12621f [Fix]: Throw error only if the popup is not displayed 2025-01-29 02:04:00 +05:30
Magesh K
5f465e680a [Roxas]: updated with upstream 2025-01-28 22:38:29 +05:30
Magesh K
2c7253c64d [Backup]: Fix: update logic during install missed since override keyword was missing 2025-01-22 04:41:46 +05:30
Magesh K
ff6a1753ee [CI]: set to BUILD_CHANNEL based on CI build type 2025-01-22 02:02:50 +05:30
Magesh K
6151681be9 [BuildInfo]: Added bundleInfo inspection to create version tag in Settings screen 2025-01-22 01:47:21 +05:30
Magesh K
37cb41d709 [CONTRIBUTING]: updated build steps 2025-01-21 23:47:46 +05:30
Magesh K
cdf336cf07 [ToastView]: Fix: restore back to printing localizedDescription as before 2025-01-21 21:54:11 +05:30
neoarz
3d1fce362e Update trustedapps.json
Signed-off-by: neoarz <164915254+neoarz@users.noreply.github.com>
2025-01-21 07:05:16 -05:00
neoarz
6e2161cca2 Update trustedapps.json
Signed-off-by: neoarz <164915254+neoarz@users.noreply.github.com>
2025-01-20 23:32:24 -05:00
Magesh K
9619567bd2 [CoreData]: Fix: propagate coredata errors properly 2025-01-20 23:03:45 +05:30
Magesh K
43941b96d0 [ErrorProcessing]: Make toast show underlying errors (if there are any) 2025-01-20 23:02:06 +05:30
Magesh K
44e0e6a591 [Settings]: Fix: removed isSelectable attribute for rows with switches 2025-01-20 19:42:45 +05:30
Magesh K
9eeb0d195e [Settings]: Fix: DisableAppLimit switch reverting back to off state even if on iOS where sparseRestore is not patched yet 2025-01-19 18:51:49 +05:30
Magesh K
5c93a1d57a [CI]: Fix: commit ID was not passed down for BUILD_REVISION properly 2025-01-19 18:07:30 +05:30
Magesh K
f26528db38 [Refresh]: Remove install stuffs from refresh since in refresh should only renew provisioning profiles (#846) 2025-01-18 21:36:41 -05:00
Magesh K
13bd4fb654 [Logging]: minimuxer: Added support for conditional logging to stdout 2025-01-14 20:03:49 +05:30
Stossy11
7ce5b96442 Clean up unneeded guard 2025-01-14 13:58:56 +11:00
Stossy11
8dd049a537 Fix missing variable 2025-01-14 13:57:35 +11:00
Stossy11
1e82564817 fix Function name 2025-01-14 13:48:45 +11:00
Stossy11
87f16a17ba Clean up SideJITServer Implementation 2025-01-14 13:47:38 +11:00
Magesh K
320bcc6f24 [diagnostics]: Added switches for OperationLogging to use them for debugging/diagnostics on device 2025-01-14 07:23:23 +05:30
Magesh K
76b678363e [Diagnostics]: Added some "intro" and "outro" standard logs for logging diagnostics 2025-01-14 01:31:19 +05:30
Magesh K
d6c062dd88 [ConsoleLogView]: Feature: Added capability to search the logs 2025-01-14 01:30:31 +05:30
Magesh K
d6e7548162 [removeExtensions]: Bug-Fix: appExtensions is not available later at async contect, so capture it prematurely when available 2025-01-13 09:33:29 +05:30
Magesh K
7ad86f236c [removeExtensions]: Bug-Fix: 1. existing App is ALTApplication not InstalledApp - corrected this 2. process as background mode if prompt can't be made or else signal error if operation context changed in-flight 2025-01-13 07:29:37 +05:30
Magesh K
f0d0f172d1 [cleanup]: fix spacing issue in echo strings 2025-01-13 03:49:43 +05:30
Magesh K
0f5fda276b [Settings]: version info now includes xcode build version if in debug config 2025-01-13 03:40:26 +05:30
Magesh K
b510bd097f [CI]: Fix wrong secrects for BUILD_LOG_ZIP_PASSWORD 2025-01-13 03:39:30 +05:30
Magesh K
a652ce863c [removeExtensions]: Refactored AppManager and moved out removeAppExtensions to fix subtle logic bugs 2025-01-13 02:30:38 +05:30
Magesh K
683c8231e3 Revert "fix: Refreshing via Xcode would cause a crash here"
This reverts commit b4a1b5ec0e.
2025-01-13 01:02:53 +05:30
Magesh K
704b75bd63 [CI]: Capture SideStore build log using 'tee' and upload it as encrypted zip in the artifacts 2025-01-12 22:43:07 +05:30
Magesh K
49f79ee125 [Makefile]: Switch to release config as default unless overriden via env-vars for xcodebuild 2025-01-12 21:27:36 +05:30
Magesh K
d5417a1809 [Settings]: Hide diagnostics section in settings screen by default on Release builds and non-beta debug builds 2025-01-12 20:59:09 +05:30
Magesh K
4586607f04 [Settings]: Fix some buttons not working and moved diagnostics switches into new section 2025-01-12 20:51:27 +05:30
Magesh K
baa9ca5357 [debug]: enabled back appropriate preprocessor debug blocks as before wherever applicable 2025-01-12 20:48:02 +05:30
Magesh K
24bf1fe07f [DataHolder]: Fixes for out-of-bounds condition and revert back to if-else instead of guard-else for better representation of logic 2025-01-12 00:03:25 +05:30
Magesh K
18bb117f53 [ActiveAppsWidget]: Fix: use invalidatable() after button to enable the button state properly after new timeline entry has arrived 2025-01-11 22:10:28 +05:30
Magesh K
e2294403dc [Widgets]: Added notes on persistence to reduce memory footprint since we can't have eviction strategy 2025-01-11 21:36:52 +05:30
Magesh K
31f20c4d30 [Widgets]: cleanup: refactored to use guard-else flow instead of if-else flow 2025-01-11 21:31:10 +05:30
Magesh K
a5074b80c7 [Widgets]: Bug-Fix: always use widgetKind for requesting timeline reload request 2025-01-11 04:10:02 +05:30
Magesh K
3a088e3d5c [Widgets]: Enhanced to isolate operations from multiple views of same widget type 2025-01-11 03:25:25 +05:30
Magesh K
976ec5962e [Widgets]: Use AppIntentConfiguration instead of StaticConfiguration and cleanup 2025-01-10 08:11:35 +05:30
Magesh K
672eda65c5 [Widgets]: (complete) Fixed previous pagination issues for ActiveAppsWidget 2025-01-09 18:51:45 +05:30
Magesh K
33cdcd6373 [Widgets]: [WIP]: Implement Pagination in ActiveAppWidget based on user interaction 2025-01-09 05:07:13 +05:30
Magesh K
1a824cab07 [Widgets]: Fix for AppDetailWidget previews crashing 2025-01-08 14:36:27 +05:30
Magesh K
cb3ae5e73a [AltBackup]: Fix the copy step for dsym and applications into xcarchive 2025-01-08 06:41:40 +05:30
Magesh K
98f93fa646 [AltBackup]: added clean target to makefile to copy fresh from the build dir 2025-01-08 06:33:21 +05:30
Magesh K
cd3a0be98a [gitignore]: .DS_STORE ignore including in subdirs 2025-01-08 06:16:36 +05:30
Magesh K
ee4befc6d0 [App-Groups]: Fix: Widgets AppGroup was mismatching from Sidestore main app 2025-01-08 05:23:13 +05:30
Magesh K
ce691aea30 [cleanup]: commented out debug-only code until CI is switched to release builds 2025-01-08 02:51:02 +05:30
Magesh K
e5ef0575e8 [Feature]: Import external backup, Restore n-1 backup if current backup is corrupted by importing wrong directory 2025-01-07 18:24:25 +05:30
Magesh K
e4c3bd0521 [ErrorLog]: Fix too much spacing in the right bar buttons in navigation bar 2025-01-02 22:20:37 +05:30
Magesh K
22118ffdb6 [diagnostics]: make operations logging into console to be conditional, toggled by a switch in settings 2025-01-02 20:59:27 +05:30
Magesh K
ab4c0225b6 [AuthScreen]: Fix toastView bg/fg color and text color 2025-01-02 20:30:16 +05:30
Magesh K
c7762df65e [refactor]: renamed package Util.debug to Util.diagnostics 2025-01-02 20:22:42 +05:30
Magesh K
79d8d6fd88 [FileName-Timestamp]: use accuracy upto millis to have unique file name 2025-01-02 20:16:21 +05:30
Magesh K
56f0a591b3 [coredata-export]: Tried implementing serialization underlying for the sqlite store when performing backup, but cannot do so because coredata might have open handle to db 2025-01-02 20:12:14 +05:30
Magesh K
73d18c3a59 [diagnostics]: Added exporting of the coredata sqlite for debugging 2025-01-02 20:05:16 +05:30
Magesh K
9371e0c7ec [CI]: post apps-v2 on main instead of nightly 2024-12-30 04:21:46 +05:30
Magesh K
cecb14df9f [CI]: use main branch for updates to apps-v2 sources.json 2024-12-30 03:54:18 +05:30
Magesh K
64bb6b9015 [CI]: apps-v2 sources.json updater - use default bundleID as sidestore 2024-12-30 03:36:28 +05:30
Magesh K
d0c3080349 [CI]: updated sources.json updater script 2024-12-30 03:27:03 +05:30
Magesh K
d30320f33f [apps-v2]: sync submodule to latest commit 2024-12-30 03:25:26 +05:30
Magesh K
6b56635a8a [CI]: updated nightly workflow to match apps-v2 2024-12-30 03:24:31 +05:30
Magesh K
5095181682 [apps-v2]: updated to push beta(nightly) updates to sources 2024-12-30 00:49:06 +05:30
Magesh K
da606b564f [apps-v2]: updated to latest commit 2024-12-30 00:48:40 +05:30
Magesh K
fe0b264eb0 [Console-Log]: Added raw console logging in ErrorLog section (ladybug icon) 2024-12-29 03:12:59 +05:30
Magesh K
4d1e6a4711 [README]: updated iOS requirement 2024-12-28 14:54:38 +05:30
Magesh K
59c8e2ff5a [Fix]: operate on UI controls in main thread 2024-12-28 02:12:03 +05:30
Magesh K
4cf72efc00 [README]: updated min iOS to 15 and added info about cocoapods requirement for development 2024-12-28 02:06:14 +05:30
Magesh K
3984809758 Reapply "[cleanup]: remove left overs from last cleanup"
This reverts commit cb99428886.
2024-12-28 01:01:06 +05:30
Magesh K
576788afbe [Pods-fix]: using "Folders" feature of xcode to organize files bumped the project objectVersion from 60 to 71(Xcode 16) which is still unsupported by latest cocoapods 2024-12-28 00:54:04 +05:30
Magesh K
87edaaccb2 [xcconfigs]: Move the missed configs and fix path for pods directory 2024-12-28 00:48:16 +05:30
Magesh K
cb99428886 Revert "[cleanup]: remove left overs from last cleanup"
This reverts commit 5dc43924d6.
2024-12-28 00:44:06 +05:30
Magesh K
5dc43924d6 [cleanup]: remove left overs from last cleanup 2024-12-27 23:38:52 +05:30
Magesh K
140498d43b [AltSign]: updated to use latest AltSign from SideStore/AltSign master 2024-12-27 23:36:53 +05:30
Magesh K
66be67e85d [cleanup]: declutter by moving the xcconfig into their own directory 2024-12-27 23:36:21 +05:30
Magesh K
50c48b417d -[AltSign]: updated submodule to latest 2024-12-27 23:13:10 +05:30
Magesh K
351704f62b [AltSign]: updated to use latest AltSign from SideStore/AltSign master 2024-12-27 22:52:30 +05:30
Magesh K
e15642d003 [xcconfig]: corrected the variable from DEBUG_BUNDLE_ID_SUFFIX to BUNDLE_ID_SUFFIX 2024-12-27 18:48:08 +05:30
Spidy123222
e6750d4fa9 Try adding backwards compatibility in trusted
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2024-12-27 00:25:52 -08:00
Magesh K
431d0079cb [trusted-apps]: update url to develop branch trustedapps.json (need to move it to proper location later) 2024-12-26 22:00:38 +05:30
Magesh K
6bc89aa4ab [CI]: update rebase.yml to alpha.yml 2024-12-26 21:15:29 +05:30
Magesh K
e5d5b436cf [rebase]: restore dropped changes 2024-12-26 21:15:29 +05:30
Magesh K
97bd312fb3 [SPM-package-cache]: Never check-in the resolved package file into SCM 2024-12-26 21:15:29 +05:30
Magesh K
97d13b2386 cleanup received from rebase-2.0-wip 2024-12-26 21:15:29 +05:30
Magesh K
947637dc71 [Icons]: added Snow icon 2024-12-26 21:15:29 +05:30
Magesh K
235f241ed5 [Settings]: added toggle for ExportResignedApps to export resigned apps to SideStore Documents dir 2024-12-26 21:15:29 +05:30
Magesh K
592cb357cf [CI]: Added conditional deployment of the alpha commit to apps-v2 sources
- (useful when debugging CI and to prevent continuous in-app update noise to the users)
2024-12-26 21:15:29 +05:30
Magesh K
7080607262 [AltBackup]: Fix: fakesign was not applied to AltBackup.ipa since it was not re-created causing missing entitlements 2024-12-26 21:15:29 +05:30
Magesh K
39fc3d8617 [AppGroupsID]: use common ID across sideStore, widget, altbackup 2024-12-26 21:15:29 +05:30
Magesh K
14f745a715 [CI]: Fix dSYM artifact upload path 2024-12-26 21:15:29 +05:30
Magesh K
912d37ff4d [View]: Fix: temporarily use hardcoded values for headerSize since double dequeue is strictly disallowed 2024-12-26 21:15:29 +05:30
Magesh K
9949e90e8c [debug]: added export capability for resigned apps and makefile cleanup 2024-12-26 21:15:29 +05:30
Magesh K
9fd8f2a766 [AltBackup]: restore dropped changes 2024-12-26 21:15:29 +05:30
Magesh K
f13a7e58ff [AltStoreCore]: cut out recursion due to customError inheritance in ALTLocalizedError for errorDescription field 2024-12-26 21:15:29 +05:30
Magesh K
0fef90b744 [CI]: upgrade to use Xcode 16.1 on macOS 14 runner 2024-12-26 21:15:29 +05:30
Magesh K
09561e83c8 [debug]: restore debug information format to "dwarf-with-dsym" 2024-12-26 21:15:29 +05:30
Magesh K
7ef59f6cec [xcconfig]: refactor AltStore xcconfig to extract out common configs 2024-12-26 21:15:29 +05:30
Magesh K
a9efbb837c [bundleID]: use BUNDLE_ID_SUFFIX for both debug/release builds and separate unsigned-altStoreCore from signed bundleID 2024-12-26 21:15:29 +05:30
Magesh K
a496118722 [gitignore]: added new ignore files 2024-12-26 21:15:29 +05:30
Magesh K
d3ecee3f5e [xcconfig]: move out PRODUCT_BUNDLE_IDENTIFIER[config=Debug] from build.xcconfig into CodeSigning template 2024-12-26 21:15:29 +05:30
Magesh K
33951a6ad7 [Makefile]: use debug config since breakpoints are not resolving 2024-12-26 21:15:29 +05:30
Magesh K
94693653a1 [CI]: export dSYMs to releases page in the artifacts section as zip 2024-12-26 21:15:29 +05:30
Magesh K
e006f59322 [AltBackup]: added AltBackup to fakesign target in makefile 2024-12-26 21:15:29 +05:30
Magesh K
39b3fb4726 [dSYMs]: updated makefile to define configuration explicitly, renamed xcarhive and retain dSYMs at original location 2024-12-26 21:15:29 +05:30
Magesh K
24b588f370 [makefile]: added target for 'clean' 2024-12-26 21:15:29 +05:30
Magesh K
343845210d [cleanup]: define operations explicitly 2024-12-26 21:15:29 +05:30
June Park
3b0c864f4c Update AltSign
and pray it doesn't break...

Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-26 21:15:29 +05:30
Magesh K
f5565321cb [Files-Support]: make sidestore app dir accessible in files app 2024-12-26 21:15:29 +05:30
Magesh K
8f0dd7c26b [ErrorLog]: Fixed text background color which was mismatching 2024-12-26 21:15:29 +05:30
Magesh K
c4dcd53472 [CI]: always try installing cocoa pods irrespective of cache 2024-12-26 21:15:29 +05:30
Magesh K
2e82680033 [deployment]: Updated minimum target to iOS15 2024-12-26 21:15:29 +05:30
Magesh K
4c4a83e90c [apps-v2]: fix source apps updater 2024-12-26 21:15:29 +05:30
Magesh K
ae94e963cc [cleanup]: removed excessive completion handlers added recently 2024-12-26 21:15:29 +05:30
Magesh K
e33497090e [git-submodule]: checkin apps-v2.json as submodule 2024-12-26 21:15:29 +05:30
Magesh K
ed2a856ee3 [apps-v2]: enable publish to github pages for sources 2024-12-26 21:15:29 +05:30
Magesh K
9573ddc8e3 [Beta-Updates]: cleanup error handling 2024-12-26 21:15:29 +05:30
Magesh K
4abd6da633 [Beta-Updates]: Fix decode key for isBeta 2024-12-26 21:15:29 +05:30
Magesh K
19c5eec9c4 [Beta-Updates]: fix verify step for beta artifact verifications 2024-12-26 21:15:29 +05:30
Magesh K
89e43b2ef9 [cleanup]: renamed new field for build revision from commitID to revision 2024-12-26 21:15:29 +05:30
Magesh K
d67e8f5648 [Beta-Updates]: use BUILD_REVISION added as field in Info.plist instead of CURRENT_PROJECT_VERSION for commit ID marker 2024-12-26 21:15:29 +05:30
Magesh K
3b36e9545c [gitignore]: added ignore to gitignore 2024-12-26 21:15:29 +05:30
Magesh K
7dd66ea1da [Beta-Updates]: use CURRENT_PROJECT_VERSION instead of MARKETING_VERSION for commit ID marker 2024-12-26 21:15:29 +05:30
Magesh K
dce6cdc84a [fetch-sources]: Disabled caching during request 2024-12-26 21:15:29 +05:30
Magesh K
6ddee71359 [Settings]: Fix UI constraints for "Enable Beta Updates" button 2024-12-26 21:15:29 +05:30
Magesh K
a32649cb44 [apps-v2-url]: updated to use apps-v2 url for beta/alpha releases until testing is complete 2024-12-26 21:15:29 +05:30
Magesh K
05ab8e392c [Beta-Updates]: added beta commit id fetch into makefile for revision 2024-12-26 21:15:29 +05:30
Magesh K
628ba49550 [Beta-Updates]: Added beta update check feature 2024-12-26 21:15:29 +05:30
Magesh K
c86bf17794 [CI]: set IS_BETA for xcodebuild to build beta IPA with commit id as MARKETING_VERSION 2024-12-26 21:15:29 +05:30
Magesh K
de31d62394 [Beta-Suuport]: Added commit ID appending to version if in beta track build 2024-12-26 21:15:29 +05:30
Magesh K
a7eb41863e [icons]: Added more icons from sidestore-next 2024-12-26 21:15:29 +05:30
Magesh K
a266565ce5 [icons]: Fix: original icon is too small in "Change App Icon" screen 2024-12-26 21:15:29 +05:30
Magesh K
49d281245f [icons]: Fix for minicon in the Change App Icon screen 2024-12-26 21:15:29 +05:30
Magesh K
fd3c671938 [icons]: Added original icon to the icon options 2024-12-26 21:15:29 +05:30
Magesh K
6711da7da3 [assets-cleanup]: removed unused references to image assets or icon assets 2024-12-26 21:15:29 +05:30
Magesh K
819f2897d7 [icons]: Added more icons for sidestore 2024-12-26 21:15:29 +05:30
Magesh K
bf3bdaa135 [cleanup]: removed unused or redundant icons and image assets 2024-12-26 21:15:29 +05:30
Magesh K
952555145a [Assets]: removed duplicate AppIcon from Assets.xcassets since it is present in Icons.xcAssets 2024-12-26 21:15:29 +05:30
Magesh K
87bb131e9e [Settings]: Fix incorrect version info displayed at the bottom 2024-12-26 21:15:29 +05:30
Magesh K
ce6c474795 [Sidestore-Icons]: remove unused icons (delta, clip) incoming from altstore 2024-12-26 21:15:29 +05:30
Magesh K
26070fb5ba [App-Space-Optimization]: remove "Include All Assets" override causing increase in Assets.car and overall app size 2024-12-26 21:15:29 +05:30
Magesh K
34b84134e3 [cleanup]: deleted unused spm package file from xcodeproj since we use xcworkspace now 2024-12-26 21:15:29 +05:30
Magesh K
c5f92f5638 [App-Size-Optimization]: use DWARF for debug mode and strip linked product when possible 2024-12-26 21:15:29 +05:30
Magesh K
2bef458a13 [App-Size-Optimization]: updated xcconfig to override Pods config when required 2024-12-26 21:15:29 +05:30
Magesh K
cf97d518db [xcode]: Do not embed pods framework since static linking is enabled 2024-12-26 21:15:29 +05:30
Magesh K
219252c2f1 [Podfile]: Use static linking for pods to bake into executable 2024-12-26 21:15:29 +05:30
Magesh K
acc6ab8155 [Anisette-Servers]: increased duration of toast for a successful refresh 2024-12-26 21:15:29 +05:30
Magesh K
4c8c0a60ca [Anisette-Servers]: fixed Refresh Servers button label spacing and alignment 2024-12-26 21:15:29 +05:30
Magesh K
2480ed6de7 [Pods-config]: split our debug and release configs to include respective pod configs 2024-12-26 21:15:29 +05:30
Magesh K
7f6072de52 [App-Size-Optimization]: Updated base configurations to use our custom xcconfigs over cocoa pods xcconfigs to set the proper inheritance precedence
- This makes the ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES use the main app target config instead of the one set in cocoa pods xcconfig properly.
- Not embedding SWIFT libraries reduces size since swift runtime is not baked into the app
- Swift runtime is already part of iOS since 12.0 and our current app min deployment target is 14.0
2024-12-26 21:15:29 +05:30
Magesh K
70f7ffb73b [Anisette-Servers]: cleanup and enhanced error handling for anisette-servers-list 2024-12-26 21:15:29 +05:30
Magesh K
7a375c0011 [Settings.Storyboard]: use iphone16 model for Storyboard layout 2024-12-26 21:15:29 +05:30
Magesh K
87a9d89af8 [Settings.Storyboard]: reset y offsets to 0.0 for auto compute 2024-12-26 21:15:29 +05:30
Stern
966ea46262 UI: Change more UI/UX reflect SideStore branding.
Signed-off-by: Stern <stern@sidestore.io>
2024-12-26 21:15:29 +05:30
Magesh K
76b47bc0d3 [CI]: Ported step to create alpha (pre-release) artifacts for github releases 2024-12-26 21:15:29 +05:30
Magesh K
318389024d [TAG]: Sync CURRENT_PROJECT_VERSION with main app and widget 2024-12-26 21:15:29 +05:30
Magesh K
7afde3d62d [TAG]: bump release version to next minor 0.6.0 2024-12-26 21:15:29 +05:30
Magesh K
b2611ba71c Revert "[CI]: diagnostics: disabled xcpretty to print full error logs"
This reverts commit 01a3b8ba9d9cc8bd24e974aa0116bc9c3e0e38ac.
2024-12-26 21:15:29 +05:30
Magesh K
eb8ec29508 [migrations]: disabled strict checking to test new installs for now 2024-12-26 21:15:29 +05:30
Magesh K
dfd5559f32 [Makefile]: Make AltBackup.ipa generation more robust 2024-12-26 21:15:29 +05:30
Magesh K
6eb6d92362 [CI]: updated to use $CODESIGNING_FOLDER_PATH or $CONFIGURATION_BUILD_DIR whichever is available 2024-12-26 21:15:29 +05:30
Magesh K
54375b5149 [CI]: diagnostics: print files and dirs in xcode derived-data upto depth 7 2024-12-26 21:15:29 +05:30
Magesh K
65721fa9ab [anisette-servers]: Fix: List was not updated since data was published from a non UI thread 2024-12-26 21:15:29 +05:30
Magesh K
bfba242a02 [CI]: diagnostics: disabled xcpretty to print full error logs 2024-12-26 21:15:29 +05:30
Magesh K
398b26b580 [TODO]: Added TODO for cleanup of Error Logging during AppOperation 2024-12-26 21:15:29 +05:30
Magesh K
d5bd473811 [Auth-Screen]: Fix: Anisette servers list wasn't ready before signin attempt causing "No valid Servers Found" 2024-12-26 21:15:29 +05:30
Magesh K
1e29e3e713 [AltBackup+Schemes]: Fixes for URL schemes throughout both AltBackup and SideStore apps 2024-12-26 21:15:29 +05:30
Magesh K
88fdc97b5d [xcode-scheme]: Create and check-in xcscheme for AltBackup 2024-12-26 21:15:29 +05:30
Magesh K
d3dffd4549 [xcode-scheme]: disabled verbose concurrency logging temporarily 2024-12-26 21:15:29 +05:30
Magesh K
baceccae56 [AltBackup]: updated makefile to directly copy over the AltBackup.app 2024-12-26 21:15:29 +05:30
Magesh K
cf5ad0c6e4 [xcode-scheme]: disabled verbose concurrency logging temporarily 2024-12-26 21:15:29 +05:30
Magesh K
60cf724615 [cleanup]: removed unused references from framework and sources 2024-12-26 21:15:29 +05:30
Magesh K
01744183b6 [cleanup]: updated .gitignore to exclude AltBackup.ipa which will be generated by build 2024-12-26 21:15:29 +05:30
Magesh K
356289b0be [AltBackup]: Fixes for swift compile errors 2024-12-26 21:15:29 +05:30
Magesh K
26f50b814e [AltBackup]: Included as target dependency of SideStore build and add run script to make build AltBackup.ipa as part of build 2024-12-26 21:15:29 +05:30
Magesh K
b7b9e56366 [diagnostics]: added logging for all operation types and their invocations 2024-12-26 21:15:29 +05:30
Magesh K
74cecb4c59 [AppManager]: Fix: Added bac missing removeAppExtensionsOperation in run operations group and improved erorr handling 2024-12-26 21:15:29 +05:30
Magesh K
3d45c136ea [op-download]: Fixed a bug where downloaded temp file was deleted before it was used 2024-12-26 21:15:29 +05:30
Magesh K
f38b74cdb7 [error-handling]: Improved Error handling for all OperationTypes in AppManager 2024-12-26 21:15:29 +05:30
Magesh K
6bcf82bf60 [cleanup]: Added TODOs in the comments 2024-12-26 21:15:29 +05:30
Magesh K
fb7ed33b18 [xcode-console]: mute warnings about duplicate classes in AuthKit and AuthUIKit 2024-12-26 21:15:29 +05:30
Magesh K
972fde9b21 [Pods]: fixes for cocoapods integration - use $(inherited) for OTHER_LDFLAGS 2024-12-26 21:15:29 +05:30
Magesh K
ebf148dfb9 [cleanup]: renamed identifiers from io.altstore.xxxx to io.sidestore.xxxx 2024-12-26 21:15:29 +05:30
Magesh K
9e600b76a7 [cleanup]: remove unused code and renamed AltStore to SideStore in sources section 2024-12-26 21:15:29 +05:30
Magesh K
3aa510d30a [cleanup]: removed unused code which was replaced by UI input text field in sources tab 2024-12-26 21:15:29 +05:30
Magesh K
cff1d83c6c [trusted-sources]: Fix: updated trustedsources.json to match that of altstore 2.0 format 2024-12-26 21:15:29 +05:30
Magesh K
20ac9edaeb [trusted-sources]: restore back trusted source fetch URL to sidestore 2024-12-26 21:15:29 +05:30
Magesh K
30c7b7e31c [Podfile]: updated KeyChainAccess to 4.2.2 2024-12-26 21:15:29 +05:30
Magesh K
0fb24f724e [cleanup]: removed unused import Roxas 2024-12-26 21:15:29 +05:30
Magesh K
904c23fd95 [altsign]: commented out code which requires release from altsign-marketplace branch 2024-12-26 21:15:29 +05:30
Magesh K
e405fd82b3 [CI]: setup CI for rebase-2.0-wip branch 2024-12-26 21:15:29 +05:30
Magesh K
07a011c3a4 [dependencies]: deinit altsign and remove altSign ref from submodules and .gitmodules 2024-12-26 21:15:29 +05:30
Magesh K
5551f619ba [altsign]: revert altsign from altstore-marketplace to sidestore-master temporarily 2024-12-26 21:15:29 +05:30
Magesh K
6adfcf983e [dependencies]: reverted back from apple's appcenter.xcframework to microsoft public apple-sdk-appcenter 2024-12-26 21:15:29 +05:30
Magesh K
10c5d03ec9 [settings]: refined style for last row in REFRESHING APPS section 2024-12-26 21:15:29 +05:30
Magesh K
078d24bcab [settings]: Fix: siri shortcut missing from settings 2024-12-26 21:15:29 +05:30
Magesh K
42e73ceaf3 [.gitmodules]: updated info regarding submodule updates 2024-12-26 21:15:29 +05:30
Magesh K
a53fea6128 [settings]: adjust tableView start offset for screen header 2024-12-26 21:15:29 +05:30
Magesh K
77aee9d857 [settings]: restore missing rows (anisette etc) from rebase under "debug" section 2024-12-26 21:15:29 +05:30
Stern
69ec9ccf48 UI: Change UI elements to SideStore branding
Signed-off-by: Stern <stern@sidestore.io>
2024-12-26 21:15:29 +05:30
Magesh K
9634cf585a -[dependencies]: updated submodules tracker with latest remote 2024-12-26 21:15:29 +05:30
Magesh K
2090aca8ea - roxas inclusion for AltStoreCore 2024-12-26 21:15:29 +05:30
Magesh K
db73f96824 - Added launch arguments for coredata debugging 2024-12-26 21:15:29 +05:30
Magesh K
c42fa445de - remove added explicit xcscheme declaration, let autocreate do its thing 2024-12-26 21:15:29 +05:30
Magesh K
698ddd8155 - cleanup: remove stale AltServer and AltDaemon files 2024-12-26 21:15:29 +05:30
Magesh K
5ad520621c - remove useless schemes for SideStore client 2024-12-26 21:15:29 +05:30
Magesh K
ee1fa8206e - git submodules - checkin (em_proxy, minimuxer, libfragmentzip) 2024-12-26 21:15:29 +05:30
Magesh K
0b8e4929d2 - updated git submodules path 2024-12-26 21:15:29 +05:30
Magesh K
6d7cac04b2 - fix libfragmentzip path - sidestore dependency refactor 2024-12-26 21:15:29 +05:30
Magesh K
d1e2d72f7f - PODS - reinstall (fix broken references in project.pbxproj) 2024-12-26 21:15:29 +05:30
Magesh K
4d7f8c6b55 - coredata verbosity enabled temporarily 2024-12-26 21:15:29 +05:30
Magesh K
9c7ef15557 refactor-changes-wip-sidestore-deps 2024-12-26 21:15:29 +05:30
Magesh K
7d972e55fb clean-checkpoint-3-project-changes 2024-12-26 21:15:29 +05:30
Magesh K
2f27328f54 clean-checkpoint-2-restore-missing 2024-12-26 21:15:29 +05:30
Magesh K
d43d1b9a9d clean-checkpoint-1 2024-12-26 21:15:29 +05:30
Riley Testut
d13972bbaf Ignores recommended sources permission errors for RELEASE builds 2024-12-26 21:15:29 +05:30
Riley Testut
24ee1c786a Removes ability to bypass permission errors for non-recommended sources 2024-12-26 21:15:29 +05:30
Riley Testut
f461f6ac5f Updates app version to 2.0rc (31) 2024-12-26 21:15:29 +05:30
Riley Testut
8b95c16353 Renames “Prototype (Inverted)” icon to “Inverted Prototype” 2024-12-26 21:15:29 +05:30
Riley Testut
cec3c79abf Fixes missing Mastodon + Twitter social icons 2024-12-26 21:15:29 +05:30
Riley Testut
ba3c4b528b [AltStoreCore] Migrates Core Data model from v16 to v17 2024-12-26 21:15:29 +05:30
Riley Testut
38d6798a5e Displays checkmark next to current alternate app icon 2024-12-26 21:15:29 +05:30
Riley Testut
f065139140 Adds remaining alternate app icons 2024-12-26 21:15:29 +05:30
Riley Testut
40efd02041 Fixes “more” button incorrectly (dis-)appearing on update cells 2024-12-26 21:15:29 +05:30
Riley Testut
aa63756dd8 Fixes not showing toast view if error occurs during initial sources fetch 2024-12-26 21:15:29 +05:30
Riley Testut
15f53b7d3e Fixes “unsatisfiable constraints” runtime error for InstalledAppsCollectionFooterView 2024-12-26 21:15:29 +05:30
Riley Testut
2602f6f8b5 Fixes missing last Coding Path value for DecodingError.keyNotFound 2024-12-26 21:15:29 +05:30
Riley Testut
f29f466833 Throws error if marketplace app is missing buildVersion 2024-12-26 21:15:29 +05:30
Riley Testut
3cfa3f46a3 Improves maketplace source error messages 2024-12-26 21:15:29 +05:30
Riley Testut
50e8e88387 Adds Patreon-related values to app analytics 2024-12-26 21:15:29 +05:30
Riley Testut
80dabcb3e3 Rethrows Core Data save errors after installing apps vs ignoring 2024-12-26 21:15:29 +05:30
Riley Testut
a9d55f0c91 Fixes “transformable properties not using secure transformer” runtime warnings 2024-12-26 21:15:29 +05:30
Riley Testut
62275c0cdd Fixes incorrect corner radius animation for AppViewController + HeaderContentViewController 2024-12-26 21:15:29 +05:30
Riley Testut
1f807c7756 Fixes missing blur when pushing AppViewController onto modal navigation controller 2024-12-26 21:15:29 +05:30
Riley Testut
e0c19f7aee Fixes not showing “more updates” button when there are more than 2 updates 2024-12-26 21:15:29 +05:30
Riley Testut
d3ded82f16 Throws error when adding marketplace source to non-marketplace AltStore (and vice versa) 2024-12-26 21:15:29 +05:30
Riley Testut
ffaf80d25d Updates Patreon sign-out alert message to apply to all pledged apps 2024-12-26 21:15:29 +05:30
Riley Testut
b533ebcea0 Supports “custom” pledge amounts for Patreon apps 2024-12-26 21:15:29 +05:30
Riley Testut
6c02e51aba Uses alternate app icon for AltStore in My Apps, if one is chosen 2024-12-26 21:15:29 +05:30
Riley Testut
e9f74d963e Supports alternate app icons 2024-12-26 21:15:29 +05:30
Riley Testut
252bea1879 Updates app icon to “modern” version 2024-12-26 21:15:29 +05:30
Riley Testut
7fccd50390 Updates social media URLs for Credits section in Settings 2024-12-26 21:15:29 +05:30
Riley Testut
e8e5cfd047 Shows “Downloading [app]…” toast view when installing app from new source
Allows users to tap it to immediately see installation progress.
2024-12-26 21:15:29 +05:30
Riley Testut
b0e812bcbc Displays version # for updates in My Apps tab 2024-12-26 21:15:29 +05:30
Riley Testut
0516897067 Adds social media follow buttons to Settings 2024-12-26 21:15:29 +05:30
Riley Testut
6967ede0e2 Dismisses PreviewAppScreenshotsViewController with swipe gesture 2024-12-26 21:15:29 +05:30
Riley Testut
e3ccb49c29 Always asks to add source when installing app if not yet added 2024-12-26 21:15:29 +05:30
Riley Testut
033b80a5d5 Fixes not updating featured apps installation status on source detail page 2024-12-26 21:15:29 +05:30
Riley Testut
609cf69715 Hides source detail screens after adding/removing source
Fixes various issues due to saving/deleting source while viewing source details.
2024-12-26 21:15:29 +05:30
Riley Testut
812a4f2b1f Changes “WiFi” spelling to “Wi-Fi” 2024-12-26 21:15:29 +05:30
Riley Testut
9258591559 Hides “REMOVE” button in navigation bar if source is already added 2024-12-26 21:15:29 +05:30
Riley Testut
4ca4e7c235 Supports JSON5 for sources 2024-12-26 21:15:29 +05:30
Riley Testut
f3b250edf4 Updates app version to 2.0b7 (30) 2024-12-26 21:15:29 +05:30
Riley Testut
ff629ff74a [AltStoreCore] Migrates Core Data model from v15 to v16 2024-12-26 21:15:29 +05:30
Riley Testut
d6b27ac72b [AltStoreCore] Makes PledgeTier.name optional to match Patreon API 2024-12-26 21:15:29 +05:30
Riley Testut
c88e1a586d [AltStoreCore] Includes more context when logging Patreon account errors
Includes full decoding path if possible.
2024-12-26 21:15:29 +05:30
Riley Testut
a358602f05 [AltStoreCore] Fixes parsing Patreon responses containing tiers with null titles 2024-12-26 21:15:29 +05:30
Riley Testut
d9e1f1b998 [AltServer] Updates app version to 1.7.2b (84) 2024-12-26 21:15:29 +05:30
Riley Testut
ba2acd3a33 Updates app build version to 25 2024-12-26 21:15:29 +05:30
Riley Testut
a70073d4ed Supports not including get-task-allow entitlement in source JSON if value is false 2024-12-26 21:15:29 +05:30
Riley Testut
d7da6a09b4 Revert "Fixes always showing non-featured apps last in FeaturedViewController"
This reverts commit f76e3a12b6.

We’re not sure we want to commit to this behavior, so reverting for now.
2024-12-26 21:15:29 +05:30
Riley Testut
c9c5ce69f5 [AltStoreCore] Migrates Core Data model from v14 to v15 2024-12-26 21:15:29 +05:30
Riley Testut
d75de228c7 Updates app version to 2.0b6 (23) 2024-12-26 21:15:29 +05:30
Riley Testut
8c5984cf35 Fixes FeaturedViewController warnings 2024-12-26 21:15:29 +05:30
Riley Testut
a197a92280 Fixes always showing non-featured apps last in FeaturedViewController 2024-12-26 21:15:29 +05:30
Riley Testut
bc67e6b2c0 Slightly decreases AppBannerView badge/source icon spacing 2024-12-26 21:15:29 +05:30
Riley Testut
d67f48291a Fixes “Unable to satisfy constraints” Auto Layout warnings 2024-12-26 21:15:29 +05:30
Riley Testut
12e7a0cf31 Actually shows AltStore build version in Settings for BETA builds 2024-12-26 21:15:29 +05:30
Riley Testut
1a3b021d3f Uses filled symbol images for category menu picker 2024-12-26 21:15:29 +05:30
Riley Testut
99013f45e7 Fixes incorrect search cancel button tint color after browsing category/source apps 2024-12-26 21:15:29 +05:30
Riley Testut
6b921e5452 Changes BrowseViewController’s search bar style to .automatic
More space efficient, and avoids UI bug where inline search bar could appear messed up after pushing view controller onto navigation stack.
2024-12-26 21:15:29 +05:30
Riley Testut
cbe747f41f Fixes button titles flashing when scrolling into view 2024-12-26 21:15:29 +05:30
Riley Testut
f2e33fdb90 Enables persisting .info level OSLogs 2024-12-26 21:15:29 +05:30
Riley Testut
2721f0ac17 Fixes using incorrect Logger in VerifyAppPledgeOperation 2024-12-26 21:15:29 +05:30
Riley Testut
a2e5a244e8 Automatically dismisses web view if user completes Patreon checkout flow 2024-12-26 21:15:29 +05:30
Riley Testut
3b5809ee18 Adds comments explaining not to rethrow errors from VerifyAppPledgeOperation 2024-12-26 21:15:29 +05:30
Riley Testut
8ef0a17cb8 Displays detailed error log in-app with Quick Look 2024-12-26 21:15:29 +05:30
Riley Testut
c69949d338 [AltStoreCore] Fixes signing-in to Patreon with Google account 2024-12-26 21:15:29 +05:30
Riley Testut
e2a79e6cbd Supports joining Patreon via web view + downloading app in single flow
Asks user to connect Patreon account if they are signed-in inside WebViewController but not in AltStore settings.
2024-12-26 21:15:29 +05:30
Riley Testut
1d8b42401f Shows source/category icon next to BrowseViewController’s title
Also tints all navigation bar buttons to match source/category tint color.

# Conflicts:
#	AltStore/Browse/BrowseViewController.swift
2024-12-26 21:15:29 +05:30
Riley Testut
db090fb747 Shows “Pledge Expired” for installed Patreon apps without active pledge 2024-12-26 21:15:29 +05:30
Riley Testut
5e16c4b3fb [AltStoreCore] Finalizes StoreCategory icons, colors, and JSON values 2024-12-26 21:15:29 +05:30
Riley Testut
808c197117 Fixes showing “Update” for Patreon apps with inactive pledges 2024-12-26 21:15:29 +05:30
Riley Testut
3925555c60 Fixes squished AppBannerView on AppIDsViewController 2024-12-26 21:15:29 +05:30
Riley Testut
094e04e614 Changes MyAppsViewController.noAppsDataSource to non-prefetching data source 2024-12-26 21:15:29 +05:30
Riley Testut
96630d2b13 Fixes incorrect initial size for website button on Source detail page 2024-12-26 21:15:29 +05:30
Riley Testut
522594b5d1 [AltStoreCore] Adds Date.shortDateFormatter 2024-12-26 21:15:29 +05:30
Riley Testut
aac1d5b14b Fixes unused variable warning for SourcesViewController preview 2024-12-26 21:15:29 +05:30
Riley Testut
e302fd4ce8 Supports searching all apps from FeaturedViewController 2024-12-26 21:15:29 +05:30
Riley Testut
59d51b73fc Limits paging app screenshots on FeaturedViewController to bottom of cell
Prioritizes paging featured apps over app screenshots.
2024-12-26 21:15:29 +05:30
Riley Testut
04c0b07c54 Randomizes featured source + app order at app launch 2024-12-26 21:15:29 +05:30
Riley Testut
2017c6029a Completely redesigns Browse tab with FeaturedViewController 2024-12-26 21:15:29 +05:30
Riley Testut
c0eca97b0a Moves caption below app + developer name in AppCardCollectionViewCell 2024-12-26 21:15:29 +05:30
Riley Testut
8acf9ca9fe Shows app’s source icon on AppBannerView
Excluding contexts where it is redundant (e.g. source detail page).
2024-12-26 21:15:29 +05:30
Riley Testut
86ea72db9e Supports filtering apps in BrowseViewController by category 2024-12-26 21:15:29 +05:30
Riley Testut
027435edce [AltStoreCore] Adds StoreApp.category + StoreCategory enum 2024-12-26 21:15:29 +05:30
Riley Testut
735fed4993 Allows changing BrowseViewController sort order 2024-12-26 21:15:29 +05:30
Riley Testut
d44e928bc5 Changes BrowseViewController’s search bar placement to inline 2024-12-26 21:15:29 +05:30
Riley Testut
35aee6fa56 Limits updating sources to app launch and manually via pull-to-refresh 2024-12-26 21:15:29 +05:30
Riley Testut
d56800a908 Asks user to review permissions when installing/updating apps
When installing, all entitlements will be shown. When updating, only _added_ entitlements will be shown.
2024-12-26 21:15:29 +05:30
Riley Testut
ec1e1721bb Fixes accidentally saving CancellationErrors to error log 2024-12-26 21:15:29 +05:30
Riley Testut
19207f9cf1 Clears image cache with “Clear Cache…” option in Settings
Also increases image cache size to 512MB.
2024-12-26 21:15:29 +05:30
Riley Testut
d239b7ad22 Adopts automatic status bar tinting on iOS 17 for App + Source detail screens 2024-12-26 21:15:29 +05:30
Riley Testut
3f56e84563 Changes NewsCollectionViewCell image aspect ratio to 3:2
Also updates fonts to use dynamic text styles.
2024-12-26 21:15:29 +05:30
Riley Testut
baf8c40037 [AltWidget] Fixes compiling for iOS 16 and earlier
Also fixes unused variable warnings.
2024-12-26 21:15:29 +05:30
Riley Testut
1e886c79e8 Hides “UPDATE” option for Patreon apps with expired pledges 2024-12-26 21:15:29 +05:30
Riley Testut
e463675439 Disables actions for Patreon apps with expired pledges instead of hiding them 2024-12-26 21:15:29 +05:30
Riley Testut
c194ba0afe Downloads latest _available_ version when updating from AppViewController
Asks user to fall back to latest supported verson if version is not compatible with device’s iOS version.
2024-12-26 21:15:29 +05:30
Riley Testut
f67ce09853 Designates Patreon apps with label + displays price (if provided) 2024-12-26 21:15:29 +05:30
Riley Testut
b5b645503e Supports updating apps from (almost) all AppBannerViews
Previously, you could only update apps from MyAppsViewController and AppViewController.
2024-12-26 21:15:29 +05:30
Riley Testut
60c33bb61e Fixes AltStore still being refreshing even after pledge expires 2024-12-26 21:15:29 +05:30
Riley Testut
2fed241d8f Fixes showing Patreon page when installing non-Patreon apps 2024-12-26 21:15:29 +05:30
Riley Testut
b13d13eac6 Supports remotely disabling workaround for downloading Patreon attachments
In case our workaround for downloading Patreon post attachments breaks, we can remotely disable it and force AltStore to use its fallback instead (taking user to post directly).
2024-12-26 21:15:29 +05:30
Riley Testut
83b3f46906 Supports downloading apps from locked Patreon posts
Uses cached Patreon session cookies to access post attachments despite no official API support.
2024-12-26 21:15:29 +05:30
Riley Testut
fde9d41aa2 Limits installed Patreon apps that no longer have active pledge
Patreon apps with inactive pledges still support these actions:
* Backed up
* Deactivated
* Export backup
2024-12-26 21:15:29 +05:30
Riley Testut
919b38b37e Switches from StoreApp.isBeta to isPledged to determine whether app is visible
If StoreApp.isHiddenWithoutPledge == false (default), we’ll still show the app.
2024-12-26 21:15:29 +05:30
Riley Testut
9dc15389fd [AltStoreCore] Renames PatreonAccount.isPatron to isAltStorePatron 2024-12-26 21:15:29 +05:30
Riley Testut
f80489adf5 Updates apps’ pledge status upon (de-)authenticating with Patreon
No longer deactivates apps whenever pledge expires.
2024-12-26 21:15:29 +05:30
Riley Testut
9c45d27184 [AltStoreCore] Caches Patreon session cookies from in-app browser
Allows us to download apps from locked Patreon posts.
2024-12-26 21:15:29 +05:30
Riley Testut
97025b3730 [AltStoreCore] Adds AppProtocol.storeApp
Simplifies retrieving the associated StoreApp for an app.
2024-12-26 21:15:29 +05:30
Riley Testut
54d9cff53a Verifies StoreApp.isPledged status when updating source 2024-12-26 21:15:29 +05:30
Riley Testut
54909ca7e7 [AltStoreCore] Adds Pledge, PledgeReward, and PledgeTier
Allows us to cache pledges for current user, which can be used to determine if user has access to Patreon-only apps.
2024-12-26 21:15:29 +05:30
Riley Testut
8f6d8d5450 [AltStoreCore] Refactors PatreonAPI to reduce duplicate logic 2024-12-26 21:15:29 +05:30
Riley Testut
4ba44d8722 [AltStoreCore] Updates StoreApp to support Patreon-exclusive apps 2024-12-26 21:15:29 +05:30
Riley Testut
f96bcb7c71 [AltStoreCore] Generalizes Source.sourceID(from:) logic into URL.normalized()
Allows comparing URLs that may have slight (but irrelevant) differences (e.g. trailing slashes).
2024-12-26 21:15:29 +05:30
Riley Testut
404709d496 [AltJIT] Updates version to 1.0.1 (2) 2024-12-26 21:15:29 +05:30
Riley Testut
b44808e856 [AltServer] Updates app version to 1.7.1 (81) 2024-12-26 21:15:29 +05:30
Riley Testut
99cd0d4995 [AltServer] Supports changing AltJIT timeout via defaults CLI 2024-12-26 21:15:29 +05:30
Riley Testut
e4128d3004 [AltJIT] Adds --timeout option to change connection timeout 2024-12-26 21:15:29 +05:30
Riley Testut
547c887586 [AltJIT] Extends RSD tunnel + debug server timeouts to 90 seconds 2024-12-26 21:15:29 +05:30
Riley Testut
6322104276 Fixes deadlock when getting/setting progress for an app 2024-12-26 21:15:29 +05:30
Riley Testut
85ab7b8b22 Updates build version to 17 2024-12-26 21:15:29 +05:30
Riley Testut
9c8c785a18 Updates placeholder text for SourcesViewController 2024-12-26 21:15:29 +05:30
Riley Testut
f23635b483 Uses constant 5pt corner radius for non-rounded screenshots
Fixes iPad corners appearing too rounded.
2024-12-26 21:15:29 +05:30
Riley Testut
8e98142740 Fixes incorrectly centering screenshot thumbnail when there’s only one visible initially 2024-12-26 21:15:29 +05:30
Riley Testut
a98b53c29a Updates app version to 2.0b5 (16) 2024-12-26 21:15:29 +05:30
Riley Testut
561b59f63c [AltStoreCore] Migrates Core Data model from v13 to v14 2024-12-26 21:15:29 +05:30
Riley Testut
fdb19ef06d Shrinks AppCardCollectionViewCell height if there are no screenshots 2024-12-26 21:15:29 +05:30
Riley Testut
39e593e799 Fixes AppBannerView sticking to safe area when scrolling 2024-12-26 21:15:29 +05:30
Riley Testut
e8936c6fff Reduces spacing between apps in BrowseViewController 2024-12-26 21:15:29 +05:30
Riley Testut
d21c746920 Replaces BrowseCollectionViewCell with AppCardCollectionViewCell
* Handles dynamic screenshot sizes
* Allows swiping through screenshots
* Supports iPhone + iPad screenshots
2024-12-26 21:15:29 +05:30
Riley Testut
a790bbf691 Supports both iPhone + iPad screenshots
Prefers showing screenshots for current device, but falls back to all screenshots if there are no relevant ones.
2024-12-26 21:15:29 +05:30
Riley Testut
ffa672f6b2 Improves paging screenshots with different aspect ratios
We now page by the smallest screenshot width to ensure we never overshoot an item.
2024-12-26 21:15:29 +05:30
Riley Testut
14bd1716d1 Supports viewing full screen app screenshots from AppViewController
[Missed] Uses layout config for PreviewAppScreenshots
2024-12-26 21:15:29 +05:30
Riley Testut
422cde9706 Accurately displays dynamically-sized screenshots in AppViewController 2024-12-26 21:15:29 +05:30
Riley Testut
82b062e93a [AltStoreCore] Updates DatabaseManager to support #Preview macro
Synchronously loads database via startForPreview(), and also erases database for DEBUG builds.
2024-12-26 21:15:29 +05:30
Riley Testut
ee7d7343a9 [AltStoreCore] Adds AppScreenshot to support dynamically-sized screenshots
Preserves StoreApp.imageURL field in database model for backwards compatibility.
2024-12-26 21:15:29 +05:30
Riley Testut
2c05bbc66f [AltTests] Fixes compiler errors 2024-12-26 21:15:29 +05:30
Riley Testut
faa4c0f17f [AltStoreCore] Generates Source.identifier from sourceURL 2024-12-26 21:15:29 +05:30
Riley Testut
5a8aab3f07 [AltStoreCore] Fixes ALTAppPrivacyPermission.synthesizedName for legacy permissions 2024-12-26 21:15:29 +05:30
Riley Testut
357103944d [Shared] Includes CodingPath in Source errors’ debug description (if available) 2024-12-26 21:15:29 +05:30
Riley Testut
00bcdec0dc Revises appPermissions JSON format
• Split into `entitlements` and `privacy` sections
• `entitlements` is an array of entitlement keys
• `privacy` is a dictionary mapping UsageDescription keys to their descriptions
2024-12-26 21:15:29 +05:30
Riley Testut
2a0fdaace0 [AltStoreCore] Fixes DatabaseManager.startForPreview() deadlock 2024-12-26 21:15:29 +05:30
Riley Testut
9589baff46 Fixes “Add/Remove Source” button title not updating after removing source 2024-12-26 21:15:29 +05:30
Riley Testut
dee5a89501 Fixes incorrect Sources tab background color in dark mode 2024-12-26 21:15:29 +05:30
Riley Testut
3ee393be56 Fixes misplaced back button 2024-12-26 21:15:29 +05:30
Riley Testut
5f93ee387f Adds AddSourceViewController to add sources by URL or from list of recommended sources 2024-12-26 21:15:29 +05:30
Riley Testut
8c42d267e2 Updates Browse tab icon 2024-12-26 21:15:29 +05:30
Riley Testut
93b01cd701 Fixes “Unable to satisfy constraints” warnings for SourcesViewController 2024-12-26 21:15:29 +05:30
Riley Testut
95c920fd3a Updates incorrect Main.storyboard frames 2024-12-26 21:15:29 +05:30
Riley Testut
b2258f32cb Refactors SourceViewController into dedicated tab
* Updates UI to use source icons + tint colors
* Adds Edit button + swipe actions
2024-12-26 21:15:29 +05:30
Riley Testut
bb5f73b37d Uses uniform height for SourceDetailContentViewController News items (iOS 17+) 2024-12-26 21:15:29 +05:30
Riley Testut
c07e5f5361 Adjusts illegible Source tint colors for SourceDetailViewController 2024-12-26 21:15:29 +05:30
Riley Testut
2707710483 Adds AppBannerView.style to switch between app and source styles
`app` banners have rounded rect icons and use a lighter version of tint color as background, while `source` banners have circular icons and use the original tint color as background.
2024-12-26 21:15:29 +05:30
Riley Testut
b61b48f0bf Adds UIColor.adjustedForDisplay() to display illegible tint colors
Adjusts illegible tint colors so that they can be displayed in the UI, e.g. by making them brighter or darker.
2024-12-26 21:15:29 +05:30
Riley Testut
3669df9d75 Adds PillButton.style to switch between pill and custom styles
`pill` style enforces minimum size + content insets, while `custom` doesn’t.
2024-12-26 21:15:29 +05:30
Riley Testut
f0e35a9b6d Adds AppIconImageView.style to switch between icon and circular styles
`icon` approximates the rounded corners of an app icon, while `circular` makes the icon a circle.
2024-12-26 21:15:29 +05:30
Riley Testut
16e6aa8e17 Posts Notification when Source is added or removed 2024-12-26 21:15:29 +05:30
Riley Testut
58ad916dc3 Limits relative date strings to “Yesterday” and “Today”
Any relative date older than “Yesterday” will be displayed as absolute date instead.
2024-12-26 21:15:29 +05:30
Riley Testut
79c7e994e8 [AltStoreCore] Adds Source.isRecommended
Also replaces legacy “Trusted Sources” references with “Recommended Sources”
2024-12-26 21:15:29 +05:30
Riley Testut
691d20ae0a [AltStoreCore] Updates DatabaseManager to support #Preview macro
Synchronously loads database via startForPreview(), and also erases database for DEBUG builds.
2024-12-26 21:15:29 +05:30
Riley Testut
7e67ab7b35 Supports exporting OSLogs from ErrorLogViewController 2024-12-26 21:15:29 +05:30
Riley Testut
99df4a1a01 Logs misc. events with OSLog
* Discovering AltServers
* Clearing app cache
* Updating Friend Zone Patrons
2024-12-26 21:15:29 +05:30
Riley Testut
3214711ddb Logs AltJIT-related events with OSLog 2024-12-26 21:15:29 +05:30
Riley Testut
3e5e03c4d3 Logs Fugu14-related events with OSLog 2024-12-26 21:15:29 +05:30
Riley Testut
fed587954d Logs sideloading-related events with OSLog 2024-12-26 21:15:29 +05:30
Riley Testut
b8d67accb5 [Apps] Moves source JSON files to separate repo 2024-12-26 21:15:29 +05:30
Riley Testut
55e086ffea Fixes misplaced Info.plist entries 2024-12-26 21:15:29 +05:30
Riley Testut
e9b315ff71 Updates ALTDeviceID to iPhone 14 Pro 2024-12-26 21:15:29 +05:30
Riley Testut
03ee8eea11 Updates app version to 2.0b4 (12) 2024-12-26 21:15:29 +05:30
Riley Testut
5bb9abb871 [AltServer] Updates app version to 1.7 (78) 2024-12-26 21:15:29 +05:30
Riley Testut
74b84afca2 [AltServer] Updates app version to 1.7rc (77) 2024-12-26 21:15:29 +05:30
Riley Testut
fcb0616bd0 Conforms RefreshAllAppsWidgetIntent to ForegroundContinuableIntent 2024-12-26 21:15:29 +05:30
Riley Testut
f169d48c9a Returns nothing from RefreshAllAppsWidgetIntent 2024-12-26 21:15:29 +05:30
Riley Testut
ad164997c5 [AltWidget] Updates Home Screen widget names and descriptions 2024-12-26 21:15:29 +05:30
Riley Testut
2abe5db730 [AltServer] Moves AnisetteDataManager to “Anisette Data” group 2024-12-26 21:15:29 +05:30
Riley Testut
609db717ca [AltServer] Fetches anisette data without Mail plug-in
Works on all macOS versions supported by AltServer.
2024-12-26 21:15:29 +05:30
Riley Testut
983393a891 Updates app version to 2.0b3 2024-12-26 21:15:29 +05:30
Riley Testut
c1a640b49a [AltWidget] Fixes widgets not appearing pre-iOS 17 2024-12-26 21:15:29 +05:30
Riley Testut
af7f929703 Actually fixes interactive widget animation continuing indefinitely 2024-12-26 21:15:29 +05:30
Riley Testut
a888e7e4e4 [AltWidget] Insets ActiveAppsWidget buttons from edge to improve tapability 2024-12-26 21:15:29 +05:30
Riley Testut
f2dc21c12f [AltWidget] Fixes AppDetailWidget not displaying app information 2024-12-26 21:15:29 +05:30
Riley Testut
8bba585dd7 [AltServer] Throws ALTServerError.deviceNotFound if altjit cannot find device
Includes custom recovery suggestion to mention connecting via USB is required for iOS 17 or later.
2024-12-26 21:15:29 +05:30
Riley Testut
af9c44d2bc Fixes interactive widget animation continuing indefinitely if error is thrown 2024-12-26 21:15:29 +05:30
Riley Testut
834a8a6506 Fixes not showing “AltServer Not Found” error when refreshing via widget 2024-12-26 21:15:29 +05:30
Riley Testut
b9859406fd [AltServer] Updates app version to 1.7b1 2024-12-26 21:15:29 +05:30
Riley Testut
caf11c2936 [AltJIT] Changes AltSign-Dynamic to not be embedded
Fixes “Cycle in dependencies” compiler error when archiving AltServer.
2024-12-26 21:15:29 +05:30
Riley Testut
f1e4c5c545 [AltWidget] Fixes ActiveAppsWidget compiler error when deployment target < iOS 17 2024-12-26 21:15:29 +05:30
Riley Testut
77aed91703 Reloads widget timelines on app launch 2024-12-26 21:15:29 +05:30
Riley Testut
cc9bfe72a9 [AltServer] Fixes exporting AltServer as generic Xcode archive 2024-12-26 21:15:29 +05:30
Riley Testut
15845e03c9 [AltServer] Fixes “SDK does not contain libarclite” error when archiving 2024-12-26 21:15:29 +05:30
Riley Testut
52407832f5 [AltJIT] Fixes “AltSign-Dynamic not found” runtime error at launch 2024-12-26 21:15:29 +05:30
Riley Testut
ebf3a3172c [AltServer] Supports enabling JIT on devices running iOS 17
AltServer embeds the AltJIT CLI tool in its app bundle and runs it as an admin subprocess.
2024-12-26 21:15:29 +05:30
Riley Testut
2d0d6ae07e [AltJIT] Removes unnecessary ALTErrorKeys.h/.m
Was originally added because AltJIT couldn’t link with AltSign, which is not true anymore.
2024-12-26 21:15:29 +05:30
Riley Testut
e45a76f990 Fixes ErrorDetailsViewController not displaying text below fold 2024-12-26 21:15:29 +05:30
Riley Testut
21555f091f [AltJIT] Adds AltJIT CLI tool to enable JIT on devices running iOS 17+
Commands:

altjit enable [app/pid] --udid [udid]
* Enables JIT for given app/process

altjit mount --udid [udid]
* Mounts personalized developer disk
2024-12-26 21:15:29 +05:30
Riley Testut
19f2a3dd29 [AltStoreCore] Fixes “any ALTAppPermission cannot conform to 'Hashable’” Xcode 15 compiler error 2024-12-26 21:15:29 +05:30
Riley Testut
5efacd6267 [AltWidget] Replaces legacy PreviewProvider previews with #Preview macro 2024-12-26 21:15:29 +05:30
Riley Testut
7bf3ce8326 [AltWidget] Refactors previous widgets to use AppsTimelineProvider 2024-12-26 21:15:29 +05:30
Riley Testut
c78d359a07 [AltWidget] Adds interactive Active Apps widget to view + refresh all active apps (iOS 17+) 2024-12-26 21:15:29 +05:30
Riley Testut
6e88787999 [AltWidget] Refactors widgets into separate files 2024-12-26 21:15:29 +05:30
Riley Testut
08f669a8c3 [AltWidget] Supports refreshing apps directly from home screen 2024-12-26 21:15:29 +05:30
Riley Testut
53ffcf317d [AltWidget] Adopts containerBackground(for:) on iOS 17 2024-12-26 21:15:29 +05:30
Riley Testut
122ded45f1 [AltWidget] Fixes incorrect home screen widget margins on iOS 17 2024-12-26 21:15:29 +05:30
Riley Testut
392affe027 Converts legacy RefreshAllIntent into App Shortcut (iOS 17+) 2024-12-26 21:15:29 +05:30
Riley Testut
92d0f1a3c0 Fixes race condition causing duplicate background refresh notifications (or none) 2024-12-26 21:15:29 +05:30
Riley Testut
23efc6a163 Fixes not refreshing AltStore last when refreshing via Shortcut
Could potentially be an issue if AltStore needs to resign itself, e.g. with AltDaemon.
2024-12-26 21:15:29 +05:30
Riley Testut
a30f087ad8 Updates build version to 6 2024-12-26 21:15:29 +05:30
Riley Testut
9bd69690b6 [Permissions] Adds entries for most known privacy permissions 2024-12-26 21:15:29 +05:30
Riley Testut
d97f1315a6 [AltStoreCore] Updates AltStore12ToAltStore13 mapping model for latest model changes
* Non-optional AppPermission.usageDescription
* Non-optional AppPermission.appBundleID
* Added AppPermission.sourceID
2024-12-26 21:15:29 +05:30
Riley Testut
222617273c [AltStoreCore] Fixes incorrectly merging permissions for same app from different sources 2024-12-26 21:15:29 +05:30
Riley Testut
c4e5bcdcdd Fixes crash when adding source with the same name as another source 2024-12-26 21:15:29 +05:30
Riley Testut
628d96442f [AltStoreCore] Makes AppPermission.usageDescription non-optional for backwards compatibility
Necessary to support switching between AltStore beta and public version.

Wraps private non-optional _usageDescription value in public accessor with optional return type to still treat it as “optional” value.
2024-12-26 21:15:29 +05:30
Riley Testut
b49f6fd660 Updates build version to 4 2024-12-26 21:15:29 +05:30
Riley Testut
5179ac6f12 Fixes “non-sendable type 'Notification?' cannot cross actor boundary” warning 2024-12-26 21:15:29 +05:30
Riley Testut
17aee9b290 Revises appPermissions format in source JSON
Before, appPermissions was one array containing all permissions of different types.

Now, we split entitlement and privacy permissions into separate “entitlements” and “privacy” child arrays.
2024-12-26 21:15:29 +05:30
Riley Testut
03e745525f Changes ALTAppPermission.isKnown to check localizedName == nil, not localizedDescription
Privacy permissions always have nil localizedDescriptions, even if they’re known.
2024-12-26 21:15:29 +05:30
Riley Testut
30dc5a3bed Updates app version to 2.0b2 2024-12-26 21:15:29 +05:30
Riley Testut
d6ccb5c301 Updates Cocoapods to 1.12.1 2024-12-26 21:15:29 +05:30
Riley Testut
8347a70d97 [Permissions] Fixes incorrect entitlement keys and revises names + descriptions 2024-12-26 21:15:29 +05:30
Riley Testut
cf9066fef1 Fixes “Check for Updates” not updating any sources if one source fails 2024-12-26 21:15:29 +05:30
Riley Testut
63c9135f71 Revises store page permissions UI (again)
* Switches back “Permissions” and “Privacy” section titles
* Shrinks privacy permissions card title font
* Combines privacy + entitlements back into single “Permissions” section
* Removes “Entitlements” section name
2024-12-26 21:15:29 +05:30
Riley Testut
7cbb2da71d Adds “Disable Response Caching” debug setting
When enabled, AltStore will ignore cached responses for certain requests and will always make a new request to the server. This is useful for development when repeatedly testing changes to remote files.

Limited to UpdateKnownSourcesOperation for now, but will eventually affect fetching sources as well.
2024-12-26 21:15:29 +05:30
Riley Testut
ebb86110ed [AltStoreCore] Migrates Core Data model from v12 to v13 2024-12-26 21:15:29 +05:30
Riley Testut
cf4d308174 [AltStoreCore] Adds Permissions.plist with definitions for most known permissions
Simpler to update over time as a separate plist rather than in source code.
2024-12-26 21:15:29 +05:30
Riley Testut
72f4bd3657 Refactors app version comparison logic to always include buildVersion
Before, whether or not the source included the buildVersion affected the comparison. If present, the buildVersion was used in comparison, if not, only the version itself was used for comparsion.

This meant it was impossible to update from a version with a buildVersion to the same version without one (e.g. going from betas to final releases). Now we _always_ consider the buildVersion in the comparsion, so an earlier entry in versions array without buildVersion can be considered “newer” even if versions match.
2024-12-26 21:15:29 +05:30
Riley Testut
f514c59bdd Revises store page permissions UI
* Switches “Permissions” and “Privacy” titles
* Makes “Entitlements” and “Other Entitlements” top-level sections
* Matches parent table view layout margins
2024-12-26 21:15:29 +05:30
Riley Testut
c59266c2cb Disables permission verification for DEBUG builds 2024-12-26 21:15:29 +05:30
Riley Testut
008e740606 Fixes incorrectly handling misc. CancellationErrors 2024-12-26 21:15:29 +05:30
Riley Testut
6770010a80 Fixes “Core Data error” when error is thrown while parsing Source JSON
‘NSCodingPath’ is an array of non-ObjC values, but because it’s an array the array itself conforms to NSSecureCoding via NSArray bridging.

We now make sure every element in an array or dictionary also conforms to NSSecureCoding to keep it in an error’s userInfo for serialization.
2024-12-26 21:15:29 +05:30
Riley Testut
8929b857cf Removes support for “background mode” permissions 2024-12-26 21:15:29 +05:30
Riley Testut
ea428b5f40 Temporarily disables verifying added permissions
We’ll re-enable once we finish the “Review Permissions” screen.
2024-12-26 21:15:29 +05:30
Riley Testut
9142d42d4e Includes more detailed info in VerificationError.addedPermission error messages
* New version
* Previous version
* Added permissions
2024-12-26 21:15:29 +05:30
Riley Testut
00d6fdb1ea Fixes erroneously showing “Unsupported Updates Available” message 2024-12-26 21:15:29 +05:30
Riley Testut
3203bcd6cb Revises Entitlements UI on app detail page 2024-12-26 21:15:29 +05:30
Riley Testut
53b30dcdb0 Fixes strong reference cycles in Source view controllers
* SourcesViewController
* SourcesDetailContentViewController
2024-12-26 21:15:29 +05:30
Riley Testut
c71fe7c469 Fixes CollapsingTextView “More” button incorrectly appearing if lineCount == maximumNumberOfLines 2024-12-26 21:15:29 +05:30
Riley Testut
f45a4c1067 Fixes AppViewController scrolling performance for apps with several privacy permissions 2024-12-26 21:15:29 +05:30
Riley Testut
e20b223c55 Redesigns store page permissions UI to show all entitlements and privacy permissions 2024-12-26 21:15:29 +05:30
Riley Testut
d52d0fc4a8 [AltStoreCore] Adds some common ALTPrivacyPermissions
* Apple Music
* Bluetooth
* Calendars
* Camera
* Face ID
* Local Network
* Microphone
* Photos
2024-12-26 21:15:29 +05:30
Riley Testut
6168e0c3f1 Supports bypassing “Undeclared Permissions” error while sources are in beta
Presents error alert that can be explicitly bypassed by user when sideloading apps with undeclared permissions, and also allows user to view all undeclared permissions.
2024-12-26 21:15:29 +05:30
Riley Testut
ff277d6106 Fixes showing “Update” button on app store page when no supported update is available 2024-12-26 21:15:29 +05:30
Riley Testut
70f3a4f07a Shows AltStore build version in Settings for BETA builds 2024-12-26 21:15:29 +05:30
Riley Testut
92ccdc8451 Verifies downloaded app’s build version matches source (if provided) 2024-12-26 21:15:29 +05:30
Riley Testut
fcf571a1ba [AltStoreCore] Renames StoreApp.latestVersionString to _version 2024-12-26 21:15:29 +05:30
Riley Testut
35e3d09f4c Supports app versions with explicit build versions
AltStore will now consider an update available if either:

* The source’s marketing version doesn’t match installed app’s version
* The source declares a build version AND it doesn’t match the install app’s build version

The installed app matches an app version if both maketing versions match, and the build versions match (if provided by the source).
2024-12-26 21:15:29 +05:30
Riley Testut
6960888ce0 Shows error alert on app launch if any added sources are blocked 2024-12-26 21:15:29 +05:30
Riley Testut
7c0f61de81 Updates SourceError.blocked recovery suggestion to list installed/blocked apps
If source is already added, the error message will list all installed apps from the source.

If adding source for first time, the error message will mention exactly which apps have been blocked from the source (if provided).
2024-12-26 21:15:29 +05:30
Riley Testut
32fa812064 [AltStoreCore] Backports iOS 15+ NSManagedObjectContext.performAndWait<T>()
Simplifies returning values and throwing errors from managed object contexts.
2024-12-26 21:15:29 +05:30
Riley Testut
7249fa8ad0 Removes unused verificationHandler property from VerifyAppOperation 2024-12-26 21:15:29 +05:30
Riley Testut
495cddf2e4 Supports blocking third-party sources
Blocked sources cannot be added by new users, or updated for existing users.
2024-12-26 21:15:29 +05:30
Riley Testut
9fcea41e14 Moves SourceError to its own source file 2024-12-26 21:15:29 +05:30
Riley Testut
9ae96eda2c Verifies all privacy + background mode permissions have usage descriptions 2024-12-26 21:15:29 +05:30
Riley Testut
3a2556709c Verifies app updates have same permissions as previously installed versions 2024-12-26 21:15:29 +05:30
Riley Testut
8b68a41caf Verifies downloaded app’s permissions match source
Renames source JSON permissions key to “appPermissions” in order to preserve backwards compatibility, since we’ve changed the schema for permissions.
2024-12-26 21:15:29 +05:30
Riley Testut
05c39cd80c Verifies source’s identifier doesn’t match existing sources when adding 2024-12-26 21:15:29 +05:30
Riley Testut
2532f160bb Verifies source’s identifier doesn’t change after refreshing 2024-12-26 21:15:29 +05:30
Riley Testut
09ad29a372 Verifies downloaded app’s version matches source 2024-12-26 21:15:29 +05:30
Riley Testut
5e4feabc36 Replaces OperationError.cancelled with CancellationError
Keeps `OperationError.cancelled` around for source-compatibility, but now returns CancellationError() instead of OperationError.
2024-12-26 21:15:29 +05:30
Riley Testut
67ac0eb400 Verifies downloaded app’s SHA-256 checksum (if specified) 2024-12-26 21:15:29 +05:30
Riley Testut
3f6cecf3ec Moves VerificationError to its own source file 2024-12-26 21:15:29 +05:30
Riley Testut
e459cc00aa Removes Psychic Paper support from VerifyAppOperation 2024-12-26 21:15:29 +05:30
Riley Testut
1cd4b03d0f [AltStoreCore] Flattens optional values when @Managed/@AsyncManaged.wrappedValue is also optional 2024-12-26 21:15:29 +05:30
Riley Testut
9499e9ec5c [AltStoreCore] Adds Managed.perform() to match AsyncManaged 2024-12-26 21:15:29 +05:30
Riley Testut
c72d067403 [AltStoreCore] Renames AsyncManaged.get() to perform()
Implies it can be used as alternative to managedObject.managedObjectContext.perform() and not just for retrieving values.
2024-12-26 21:15:29 +05:30
Riley Testut
5379501639 [Shared] Adds @UserInfoValue property wrapper for ALTLocalizedErrors
ALTLocalizedErrors now automatically include all properties annotated with @UserInfoValue in userInfo when bridged to NSError.
2024-12-26 21:15:29 +05:30
Riley Testut
a567c63582 Updates app version to 2.0b1 🎉 2024-12-26 21:15:29 +05:30
Riley Testut
e3a7207acc Fixes internal location of OperatingSystemVersion+Comparable in Xcode project 2024-12-26 21:15:29 +05:30
Riley Testut
a6ef07450d [AltStoreCore] Migrates Core Data model from v11 to v12 2024-12-26 21:15:29 +05:30
Riley Testut
b3d67655ad [Apps-Alpha] Updates AltStore to 2.0a1 2024-12-26 21:15:29 +05:30
Riley Testut
a4bafb934e [Apps-Alpha] Updates to match AltStore 2.0b1 JSON format 2024-12-26 21:15:29 +05:30
Riley Testut
1c32487ff7 Fixes PillButton not respecting progressTintColor under certain conditions 2024-12-26 21:15:29 +05:30
Riley Testut
9dfc56d0fd Removes unused NavigationBar.backgroundColorView 2024-12-26 21:15:29 +05:30
Riley Testut
1708b5409a Defines explicit error codes for OperationError.Code cases 2024-12-26 21:15:29 +05:30
Riley Testut
c5f9896fab [AltStoreCore] Replaces remaining Bundle.appGroups.first with Bundle.altstoreAppGroup
Ensures we can still find the correct AltStore app group even if it isn’t the first one listed in the Info.plist.
2024-12-26 21:15:29 +05:30
Riley Testut
2917ebb5e4 [AltStoreCore] Fixes migration error on launch if AltStore app group does not exist.
Allows falling back to using regular app sandbox instead of app group.
2024-12-26 21:15:29 +05:30
Riley Testut
d4378bbe57 [AltStoreCore] Fixes incorrectly merging app versions for same app from different sources 2024-12-26 21:15:29 +05:30
Riley Testut
14ad785cd3 Fixes tapping buttons underneath navigation bar on SourceDetailViewController/AppViewController 2024-12-26 21:15:29 +05:30
Riley Testut
ad92cb6c42 Dynamically disables interactive back gesture when viewing source header image
Only disables gesture if touches are within headerContainerView to ensure back gesture works as expected majority of the time.
2024-12-26 21:15:29 +05:30
Riley Testut
626cd8b814 Updates AppViewController to use UINavigationBarAppearance APIs
Fixes visual bugs when transitioning to/from SourceDetailViewController.
2024-12-26 21:15:29 +05:30
Riley Testut
ed2f750b7b Supports adding/removing source from SourceDetailViewController 2024-12-26 21:15:29 +05:30
Riley Testut
b7dc40ba03 [AltStoreCore] Adds async wrappers for presenting UIAlertControllers 2024-12-26 21:15:29 +05:30
Riley Testut
653b84376a Opens source’s website in-app upon tapping link in SourceHeaderView 2024-12-26 21:15:29 +05:30
Riley Testut
a0991f21eb Supports viewing all NewsItems and StoreApps for a source 2024-12-26 21:15:29 +05:30
Riley Testut
9265fd5a5d Shows detailed source “About” page when adding 3rd-party sources
Allows users to preview sources before adding them to their AltStore.
2024-12-26 21:15:29 +05:30
Riley Testut
a3e67a9adb Refactors SourcesViewController from Main.storyboard to new Sources.storyboard
Also refactors BannerCollectionViewCell to AppBannerCollectionViewCell to support initializing from code.
2024-12-26 21:15:29 +05:30
Riley Testut
57837d37dd [AltStoreCore] Adds Source.isAdded
Convenience property to determine whether a source has been added to the user’s AltStore.
2024-12-26 21:15:29 +05:30
Riley Testut
f3493e0a9e [AltStoreCore] Adds @AsyncManaged property wrapper
Same as @Managed, except it supports using Swift Concurrency to fetch values from its managedObject’s managedObjectContext.
2024-12-26 21:15:29 +05:30
Riley Testut
fc4a8b09a5 [AltStoreCore] Adds NSManagedObjectContext.performAsync() to wrap iOS 15+ async perform()
Allows us to use Swift Concurrency with Core Data pre-iOS 15.
2024-12-26 21:15:29 +05:30
Riley Testut
b8e8ce8aac [AltStoreCore] Fixes incorrect Source.featuredApps relationship post-merging 2024-12-26 21:15:29 +05:30
Riley Testut
f89562eab9 Fixes CollapsingTextView “TextKit 1 compatibility mode” runtime warning 2024-12-26 21:15:29 +05:30
Riley Testut
256b0c14f5 [AltStoreCore] Supports additional source JSON values for detailed “About” page 2024-12-26 21:15:29 +05:30
Riley Testut
403b34c39c Fixes error fetching Friend Zone patrons due to unexpected nil name 2024-12-26 21:15:29 +05:30
Riley Testut
b12e33d5c9 Removes unnecessary @available annotations 2024-12-26 21:15:29 +05:30
Riley Testut
ae11777359 Removes unnecessary #available checks 2024-12-26 21:15:29 +05:30
Riley Testut
f892be0019 Fixes UIApplication.setMinimumBackgroundFetchInterval() deprecation warning 2024-12-26 21:15:29 +05:30
Riley Testut
a23753588c Fixes peek & pop deprecation warnings 2024-12-26 21:15:29 +05:30
Riley Testut
8fb0304728 Fixes UIApplication.keyWindow deprecation warning 2024-12-26 21:15:29 +05:30
Riley Testut
74a0d0f580 Fixes Scanner.scanHexInt32 deprecation warning 2024-12-26 21:15:29 +05:30
Riley Testut
36c965959b Fixes UIActivityIndicatorView.style deprecation warnings 2024-12-26 21:15:29 +05:30
Riley Testut
9eb1029fb4 [Shared] Updates projects to recommended settings (Xcode 14.1) 2024-12-26 21:15:29 +05:30
Riley Testut
d4f98f3a94 [AltStoreCore] Fixes Core Data “Using nil or insecure value transformer” warnings 2024-12-26 21:15:29 +05:30
Riley Testut
e31d7f21f0 Fixes “Plain Style unsupported in a Navigation Item” warnings 2024-12-26 21:15:29 +05:30
Riley Testut
9c35c9b1c1 Silences “Double-quoted include in framework header” warnings 2024-12-26 21:15:29 +05:30
Riley Testut
3505a07482 Fixes “App doesn’t declare if it can open files in-place” warning 2024-12-26 21:15:29 +05:30
Riley Testut
bd0a6982d4 [Shared] Updates CocoaPods dependencies 2024-12-26 21:15:29 +05:30
Riley Testut
a452376ac5 [Shared] Raises deployment targets to iOS 14.0 and macOS 11.0 2024-12-26 21:15:29 +05:30
Riley Testut
5208e19b1f [AltTests] Replaces iOS 16+ URL(filePath:) with URL(fileURLWithPath:) 2024-12-26 21:15:29 +05:30
Riley Testut
c8a3ff0a9c Fixes triggering false positives with some malware detectors
Renames UserDefaults.isMacDirtyCowSupported to .isCowExploitSupported
2024-12-26 21:15:29 +05:30
Riley Testut
b59c59de4a Adds “Clear Cache” description to Techy Things section footer 2024-12-26 21:15:29 +05:30
Riley Testut
a5b1a320de Hides MacDirtyCow settings on iOS 15.7.2
MacDirtyCow supports iOS 14.0 - 15.7.1 and 16.0 - 16.1.2, but not 15.7.2
2024-12-26 21:15:29 +05:30
Riley Testut
7042e9acbe Fixes SourcesViewController crash on iOS 12
Apparently changing NSLayoutConstraint priorities from required to optional (and vice versa) isn’t supported, even though it works on iOS 13+. Who knew!
2024-12-26 21:15:29 +05:30
Riley Testut
a2c05c8099 Fixes incorrect “View Error Log” cell appearance 2024-12-26 21:15:29 +05:30
Riley Testut
68d49bd1ca Force-enables “Enforce 3-App Limit” if iOS version does not support MacDirtyCow exploit
Prevents “Enforce 3-App Limit” remaining enabled after updating iOS version without a way to disable it.
2024-12-26 21:15:29 +05:30
Riley Testut
05ef72ddfd Adds “Clear Cache” button to remove temporary files and uninstalled app backups 2024-12-26 21:15:29 +05:30
Riley Testut
07d5a7551f Supports sideloading more than 3 apps via MacDirtyCow exploit
The MacDirtyCow exploit allows users to remove the 3 active apps limit on iOS 16.1.2 and earlier. To support this, we’ve added a new (hidden) “Enforce 3-App Limit” setting that can be disabled to allow sideloading more than 3 apps.
2024-12-26 21:15:29 +05:30
Riley Testut
d76b258073 Fixes non-readable error toast view when an authentication error occurs 2024-12-26 21:15:29 +05:30
Riley Testut
31d5d024dd Caches MergeErrors when refreshing sources to view later in SourcesViewController 2024-12-26 21:15:29 +05:30
Riley Testut
47fb8ff3eb Fixes incorrect StoreApp.versions order post-merge 2024-12-26 21:15:29 +05:30
Riley Testut
3e6f48b1bb [Shared] Fixes pattern matching non-ALTErrorEnum error codes 2024-12-26 21:15:29 +05:30
Riley Testut
19a405fe56 Fixes adding failures to NSErrors with nil localizedFailureReasons 2024-12-26 21:15:29 +05:30
Riley Testut
3e6c08f458 [AltTests] Enables Code Coverage 2024-12-26 21:15:29 +05:30
Riley Testut
5c942dc150 [AltTests] Adds error handling tests
Passes all tests

[Review] Refactors tests to be more readable

Removes unnecessary code
2024-12-26 21:15:29 +05:30
Riley Testut
ce0e5d6c50 Adds AltTests test target 2024-12-26 21:15:29 +05:30
Riley Testut
6396794963 Moves “View Error Log” setting to new Techy Things section 2024-12-26 21:15:29 +05:30
Riley Testut
f4856bd435 Fixes “missing provisioning profile” error when refreshing DEBUG builds
Removes embedded XCTest (+ dSYM) bundles before resigning for DEBUG builds.
2024-12-26 21:15:29 +05:30
Riley Testut
4e2d62cf95 [Shared] Ignores ALTWrappedError NSLocalizedDescription user info value if it == failure + failure reason 2024-12-26 21:15:29 +05:30
Riley Testut
1741aff153 [Shared] Encodes all CodableError codable user info values, not just recognized types 2024-12-26 21:15:29 +05:30
Riley Testut
9b50d2269c [Shared] Fixes error encoding CodableError Int/UInt user info values 2024-12-26 21:15:29 +05:30
Riley Testut
f03507e348 [Shared] Uses underlying error messages (if available) for ALTServerError.invalidRequest/.invalidResponse 2024-12-26 21:15:29 +05:30
Riley Testut
c34bcce5ea Updates VerificationError.errorDescription to match ALTLocalizedError default implementation 2024-12-26 21:15:29 +05:30
Riley Testut
5bb677f6d9 Fixes refreshing tweaked apps with removed app extensions
In addition to removing the app extensions themselves, we also need to remove references to them from SC_Info/Manifest.plist in the app bundle (if the file exists). Otherwise, subsequent installations (resigning, (de)-activating, etc.) will fail due to “missing” app extensions.
2024-12-26 21:15:29 +05:30
Riley Testut
c9d64986bd Revises “check for updates” error title 2024-12-26 21:15:29 +05:30
Riley Testut
46aaf8d0ce Verifies Sources don’t contain duplicate app versions 2024-12-26 21:15:29 +05:30
Riley Testut
333e68a859 Replaces StoreApp.setVersions() preconditionFailure with runtime error
It’s more common than expected for apps to not have any app versions, so better to fail gracefully than crash.
2024-12-26 21:15:29 +05:30
Riley Testut
3d586f2c64 Moves @Managed to AltStoreCore 2024-12-26 21:15:29 +05:30
Riley Testut
bb560ed8b6 Verifies Sources don’t contain duplicate bundle IDs
AltStore assumes all apps have unique bundle IDs per source. Weird bugs can occur when this is not the case (such as merging multiple store listings into one), so we now verify upfront whether source contains duplicate bundle IDs before saving.
2024-12-26 21:15:29 +05:30
Riley Testut
62518be628 [AltServer] Downloads latest supported AltStore version for device OS version
Asks user to install latest compatible version instead if latest AltStore version does not support their device’s OS version.
2024-12-26 21:15:29 +05:30
Riley Testut
e7b70328ea Fixes crash due to accessing AppManager.installationProgress/refreshProgress concurrently 2024-12-26 21:15:29 +05:30
Riley Testut
a6d9e32dfe Fixes delay updating UI after cancelling installing app 2024-12-26 21:15:29 +05:30
Riley Testut
2e27447e36 [Shared] Adds OperatingSystemVersion+Comparable to AltServer target 2024-12-26 21:15:29 +05:30
Riley Testut
0759bccfdc Includes the invalid name in error message for registering App ID with invalid characters 2024-12-26 21:15:29 +05:30
Riley Testut
44bec0fada Adds comment to ErrorLogViewController 2024-12-26 21:15:29 +05:30
Riley Testut
f45deb5ba4 Fixes missing app icon for update errors in Error Log 2024-12-26 21:15:29 +05:30
Riley Testut
c669260115 Supports updating apps from AppViewController
Unlike MyAppsViewController, AppViewController will attempt to update to the latest available version, rather than the latest supported version. If the latest version is not supported, it will fall back to asking user to install last supported version.
2024-12-26 21:15:29 +05:30
Riley Testut
7c9d501c02 Fixes updating apps to latest version instead of latest supported version from My Apps tab 2024-12-26 21:15:29 +05:30
Riley Testut
b63adb38d8 Fixes potentially incorrect versions order when merging StoreApp 2024-12-26 21:15:29 +05:30
Riley Testut
e77142a1dd Fixes incorrect update notifications for apps with unsupported versions 2024-12-26 21:15:29 +05:30
Riley Testut
c38ad14999 Adds pull-to-refresh to check for updates 2024-12-26 21:15:29 +05:30
Riley Testut
faad089801 Allows viewing unsupported updates from My Apps tab
When unsupported updates are available, the “No Updates Available” text becomes “Unsupported Updates Available”, and a button is revealed that will list all unsupported updates in an alert when tapped.
2024-12-26 21:15:29 +05:30
Riley Testut
fdbb40a0df Hides app updates that don’t support device’s OS version 2024-12-26 21:15:29 +05:30
Riley Testut
02f1c2be61 Improves error message when file does not exist at AppVersion.downloadURL 2024-12-26 21:15:29 +05:30
Riley Testut
c89dc256ff Verifies min/max OS version before downloading app + asks user to download older app version if necessary 2024-12-26 21:15:29 +05:30
Riley Testut
03b5ee840a Supports non-NSManagedObjects for @Managed properties
This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before.
2024-12-26 21:15:29 +05:30
Riley Testut
0aa3b05167 Conforms OperatingSystemVersion to Comparable 2024-12-26 21:15:29 +05:30
Riley Testut
3e9463c434 Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion
We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors.

However, we kept the underlying Core Data property name the same to avoid extra migration.
2024-12-26 21:15:29 +05:30
Riley Testut
3c4c563ef8 [AltServer] Adds “Search FAQ” button to ErrorDetailsViewController 2024-12-26 21:15:29 +05:30
Riley Testut
3c1a01a78b Skips logging OperationError.cancelled errors 2024-12-26 21:15:29 +05:30
Riley Testut
19463281cc Limits quitting other AltStore processes to database migrations only
Previously, AltStore would quit all other processes when first accessing the database no matter what. However, this unintentionally caused the widget extension to quit the main app after refreshing apps.

Now, we only quit other AltStore processes if a database migration is required. This still prevents multiple AltStores with different database schemas from accessing database concurrently, but also allows extensions to access database without quitting main app.
2024-12-26 21:15:29 +05:30
Riley Testut
936b29c04e Fixes Error Log not showing UIAlertController on iOS 13 or earlier 2024-12-26 21:15:29 +05:30
Riley Testut
9f2dfaf55c Fixes incorrect “Search FAQ” URL in Error Log 2024-12-26 21:15:29 +05:30
Riley Testut
704b5f19ab Fixes Error Log context menu appearing while scrolling table view 2024-12-26 21:15:29 +05:30
Riley Testut
df7e60eb91 Opens Error Log upon tapping ToastView showing logged error 2024-12-26 21:15:29 +05:30
Riley Testut
610c199202 Includes “Enable JIT” errors in Error Log 2024-12-26 21:15:29 +05:30
Riley Testut
6a830ea345 [Shared] Refactors error handling based on ALTLocalizedError protocol (#1115)
* [Shared] Revises ALTLocalizedError protocol

* Refactors errors to conform to revised ALTLocalizedError protocol

* [Missing Commit] Remaining changes for ALTLocalizedError

* [AltServer] Refactors errors to conform to revised ALTLocalizedError protocol

* [Missing Commit] Declares ALTLocalizedTitleErrorKey + ALTLocalizedDescriptionKey

* Updates Objective-C errors to match revised ALTLocalizedError

* [Missing Commit] Unnecessary ALTLocalizedDescription logic

* [Shared] Refactors NSError.withLocalizedFailure to properly support ALTLocalizedError

* [Shared] Supports adding localized titles to errors via NSError.withLocalizedTitle()

* Revises ErrorResponse logic to support arbitrary errors and user info values

* [Missed Commit] Renames CodableServerError to CodableError

* Merges ConnectionError into OperationError

* [Missed Commit] Doesn’t check ALTWrappedError’s userInfo for localizedDescription

* [Missed] Fixes incorrect errorDomain for ALTErrorEnums

* [Missed] Removes nonexistent ALTWrappedError.h

* Includes source file and line number in OperationError.unknown failureReason

* Adds localizedTitle to AppManager operation errors

* Fixes adding localizedTitle + localizedFailure to ALTWrappedError

* Updates ToastView to use error’s localizedTitle as title

* [Shared] Adds NSError.formattedDetailedDescription(with:)

Returns formatted NSAttributedString containing all user info values intended for displaying to the user.

* [Shared] Updates Error.localizedErrorCode to say “code” instead of “error”

* Conforms ALTLocalizedError to CustomStringConvertible

* Adds “View More Details” option to Error Log context menu to view detailed error description

* [Shared] Fixes NSError.formattedDetailedDescription appearing black in dark mode

* [AltServer] Updates error alert to match revised error logic

Uses error’s localizedTitle as alert title.

* [AltServer] Adds “View More Details” button to error alert to view detailed error info

* [AltServer] Renames InstallError to OperationError and conforms to ALTErrorEnum

* [Shared] Removes CodableError support for Date user info values

Not currently used, and we don’t want to accidentally parse a non-Date as a Date in the meantime.

* [Shared] Includes dynamic UserInfoValueProvider values in NSError.formattedDetailedDescription()

* [Shared] Includes source file + line in NSError.formattedDetailedDescription()

Automatically captures source file + line when throwing ALTErrorEnums.

* [Shared] Captures source file + line for unknown errors

* Removes sourceFunction from OperationError

* Adds localizedTitle to AuthenticationViewController errors

* [Shared] Moves nested ALTWrappedError logic to ALTWrappedError initializer

* [AltServer] Removes now-redundant localized failure from JIT errors

All JIT errors now have a localizedTitle which effectively says the same thing.

* Makes OperationError.Code start at 1000

“Connection errors” subsection starts at 1200.

* [Shared] Updates Error domains to revised [Source].[ErrorType] format

* Updates ALTWrappedError.localizedDescription to prioritize using wrapped NSLocalizedDescription as failure reason

* Makes ALTAppleAPIError codes start at 3000

* [AltServer] Adds relevant localizedFailures to ALTDeviceManager.installApplication() errors

* Revises OperationError failureReasons and recovery suggestions

All failure reasons now read correctly when preceded by a failure reason and “because”.

* Revises ALTServerError error messages
All failure reasons now read correctly when preceded by a failure reason and “because”.

* Most failure reasons now read correctly when preceded by a failure reason and “because”.
* ALTServerErrorUnderlyingError forwards all user info provider calls to underlying error.

* Revises error messages for ALTAppleAPIErrorIncorrectCredentials

* [Missed] Removes NSError+AltStore.swift from AltStore target

* [Shared] Updates AltServerErrorDomain to revised [Source].[ErrorType] format

* [Shared] Removes “code” from Error.localizedErrorCode

* [Shared] Makes ALTServerError codes (appear to) start at 2000

We can’t change the actual error codes without breaking backwards compatibility, so instead we just add 2000 whenever we display ALTServerError codes to the user.

* Moves VerificationError.errorFailure to VerifyAppOperation

* Supports custom failure reason for OperationError.unknown

* [Shared] Changes AltServerErrorDomain to “AltServer.ServerError”

* [Shared] Converts ALTWrappedError to Objective-C class

NSError subclasses must be written in ObjC for Swift.Error <-> NSError bridging to work correctly.

* Fixes decoding CodableError nested user info values
2024-12-26 21:15:29 +05:30
Riley Testut
9ea4a1d071 Updates app version to 1.6b2 2024-12-26 21:15:29 +05:30
Riley Testut
ccb4799a65 [AltWidget] Adds “icon” style lock screen widget 2024-12-26 21:15:29 +05:30
Riley Testut
c2b95a23a2 [AltWidget] Replaces ProgressRing with SwiftUI.Gauge 2024-12-26 21:15:29 +05:30
Riley Testut
503b953c93 Migrates Core Data model from v10 to v11 2024-12-26 21:15:29 +05:30
Riley Testut
bf8d90a128 Supports new “versions” key in source JSON
Allows sources to list multiple versions of an app.

Preserves backwards compatibility by assigning legacy version values when assigning AppVersions.
2024-12-26 21:15:29 +05:30
Riley Testut
200509dc79 Adds AppVersion Core Data entity
Preserves redundant fields on StoreApp in database model for backwards compatibility.
2024-12-26 21:15:29 +05:30
Riley Testut
272f1521aa Adds Error Log screen
Allows users to view a history of all errors that occured when performing app operations.
2024-12-26 21:15:29 +05:30
Riley Testut
f28e42a03c Adds LoggedError Core Data entity
Allows us to save certain errors to disk so that they can be viewed again later from an error log.
2024-12-26 21:15:29 +05:30
Magesh K
ddee526f5d [CI]: prepare nightly for merging rebase-2.0-wip 2024-12-26 21:05:15 +05:30
June Park
913ae9fab0 [skipci] Bump to 0.5.10 for new nightly cycle
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 22:58:19 +09:00
June Park
730bf2a727 Update stable.yml
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 22:48:48 +09:00
June
728da8060a attempt to fix long standing bug in bundle ids 2024-12-24 22:37:04 +09:00
June Park
30c8e696d9 Update stable.yml
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 22:09:21 +09:00
June Park
57776198e5 Update pr.yml
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 22:08:52 +09:00
June Park
785dc76250 Update nightly.yml
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 21:40:57 +09:00
June Park
443fc66b2f Use xcbeautify for nightlys
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 21:38:45 +09:00
June Park
8c3b54c695 Merge pull request #803 from neoarz/patch-1
Update README.md
2024-12-24 21:10:16 +09:00
June Park
df448d05b1 Merge pull request #809 from SideStore/junepark678/feat/updatealtsign
Add entitlements and other things
2024-12-24 19:29:39 +09:00
June Park
9e6951d5b7 Update Package.resolved
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 18:08:51 +09:00
June Park
67865937b9 Update Package.resolved
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 18:04:24 +09:00
June Park
9e681e1cee Update project.pbxproj
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 18:00:15 +09:00
June Park
fc8a90387b Update AppManager.swift
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 17:56:40 +09:00
June
dcdb4ab5e8 update altsign 2024-12-24 17:56:40 +09:00
June
7534676ce3 work harder 2024-12-24 17:56:40 +09:00
June
ea31b39dbd update again 2024-12-24 17:56:40 +09:00
June
dccfe276f5 update altsign again 2024-12-24 17:56:40 +09:00
June Park
33d5246bfc cache harder
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 16:39:59 +09:00
June
93ba28e939 fixes 2024-12-24 15:15:23 +09:00
June
b1fc8cfde9 fix roxas 2024-12-24 14:37:41 +09:00
June
eb29a5e106 fix build 2024-12-24 14:33:50 +09:00
June
99c19d6489 things 2024-12-24 14:29:50 +09:00
Rose
8309b6e3be Update StoreApp.swift
Signed-off-by: Rose <cool5tarXV@gmail.com>
2024-12-24 14:26:10 +09:00
Rose
a415adf7c1 added fix + todo
Signed-off-by: Rose <cool5tarXV@gmail.com>
2024-12-24 14:26:10 +09:00
June P
b4a6f27249 fix altsign 2024-12-23 01:08:20 +09:00
June P
7b354ed9cb Revert "Update AltSign to newer revision"
This reverts commit 1bb33a735d.
2024-12-23 01:07:24 +09:00
June
1bb33a735d Update AltSign to newer revision 2024-12-23 00:44:26 +09:00
neoarz
5d0ea94ac9 Update README.md
discord link was expired

Signed-off-by: neoarz <164915254+neoarz@users.noreply.github.com>
2024-12-19 22:49:06 -05:00
Magesh K
4dd0cf4a91 [dependencies]: updated Package.resolved to use latest commit for altsign 2024-12-12 20:52:09 +05:30
Magesh K
c5226f8b71 [settings]: refined style for last row in REFRESHING APPS section 2024-12-12 20:11:57 +05:30
Magesh K
318dae8fb3 [settings]: Fix: siri shortcut missing from settings 2024-12-12 20:11:57 +05:30
Magesh K
dc797cf646 [fetch-prebuilt.sh]: force download when libs are missing locally and skip file is absent (#796)
* [fetch-prebuilt.sh] - retain checked-in files from minimuxer repo (assuming minimuxer repo was cloned)

* [fetch-prebuilt.sh]: force download when libs are missing locally and skip file is absent
2024-12-12 00:53:57 -05:00
Magesh K
0fffd24650 [fetch-prebuilt.sh] - retain checked-in files from minimuxer repo (assuming minimuxer repo was cloned) 2024-12-08 20:21:37 +05:30
Magesh K
8e44d7d34a updated fetch-prebuilt.sh to reflect libem_proxy-ios.a renaming 2024-12-08 19:37:27 +05:30
Magesh K
fd1cdcea93 Updated LIB_FILE_NAME used in minimuxer shell script to link the binary 2024-12-08 19:30:38 +05:30
Magesh K
f08cbd11b5 - renamed libem_proxy.a to libem_proxy-ios.a for iOS builds 2024-12-08 19:05:23 +05:30
Magesh K
e7bbea602f Added launch arguments for coredata debugging and updated Scheme from AltStore to SideStore (#785) 2024-12-08 07:50:33 -05:00
ny
b4a1b5ec0e fix: Refreshing via Xcode would cause a crash here 2024-11-30 00:50:10 -05:00
Magesh K
aab45db88a - Show Toast regarding server switch due to fallback if UI context is available 2024-11-28 22:01:47 +05:30
Magesh K
c92bbaf2e9 - Fix: AnisetteServers: refresh server request shouldn't use local cache to get proper updates 2024-11-28 19:12:50 +05:30
Magesh K
b58f082d78 - Fix: refresh(): app was being accessed after de-alloc causing EXC_BREAKPOINT 2024-11-28 18:49:53 +05:30
Magesh K
edda4fab5c - Fix: Anisette-server-fallback: Try currently selected server first 2024-11-28 18:46:08 +05:30
Magesh K
def8e969a6 -[Feature]: client-side: Anisette server fallback impl when current server is unreachable (#773) 2024-11-27 18:38:54 -05:00
Magesh K
850f4704e2 - Fix: DatabaseManager.migrateDatabaseToAppGroupIfNeeded() src and dest for replaceAt() cannot be same 2024-11-28 02:47:01 +05:30
Stern
c7909ee9f7 Merge pull request #770 from mahee96/develop 2024-11-24 13:23:43 -05:00
Magesh K
efb3b26da0 Fix: update sign-in screen toast text color to be primary color 2024-11-24 23:06:39 +05:30
Magesh K
69721000df -[xcode]: revert enabling debug dylib support for widgets 2024-11-24 19:16:18 +05:30
Stephen
b669e19780 Twitter Updates & Email Support (#762) 2024-11-21 12:12:47 -05:00
Stern
b1f224e50e (chore:) Update Twitter Link
Signed-off-by: Stern <stern@sidestore.io>
2024-11-19 16:00:48 -05:00
Joe Mattiello
32e173e4bf Change gitignore, add package.resolve (#748) 2024-11-11 23:10:44 -05:00
kaoneko
39b49bdce3 Update message about Disable Idle Timeout setting (#747) 2024-11-11 15:12:49 -05:00
Magesh K
a48c3b2987 Widgets-Fix: Ported Widget compatibility for ios 18+ from altstore (#746) 2024-11-11 14:46:33 -05:00
Magesh K
55faf0c2d3 Background and shortcut refresh fix - remove only excess extensions from new package during install (#743) 2024-11-10 13:01:31 -05:00
Magesh K
835d4d39d0 Partial-Fix for #723: Error on refresh operation must be set on self.context instead of group.context (#742) 2024-11-10 06:11:44 -05:00
Magesh K
c28af95506 -[diagnostics]: Added diagnostics for RefreshAppOperation failure 2024-11-10 16:31:28 +05:30
Magesh K
e597b197d0 -[cleanup]: added guard check to remove file only if it exists 2024-11-10 16:27:29 +05:30
Magesh K
dacc0c98ab -[bug-fix]: UI Api invocation needs to be on UI Thread 2024-11-10 16:27:29 +05:30
Michael
6c32430329 Add missing = sign (#739) 2024-11-09 22:20:23 -05:00
Stern
e843a070c5 Merge pull request #738 from mahee96/develop
-[cleanup]: restore changes dropped from altstore-1.6.3 merge by 4989c42
2024-11-09 19:05:59 -05:00
Magesh K
51792f2da5 restore changes dropped from altstore by 4989c42 2024-11-10 03:04:24 +05:30
Magesh K
8f37751236 diagnostics: improved error logging for OperationError.invalidParameters (#736) 2024-11-09 04:05:18 -05:00
Stern
c7c760ced0 chore: We need to add Homebrew to our path or Xcode will not find it.
Signed-off-by: Stern <stern@sidestore.io>
2024-11-08 23:56:27 -05:00
Stern
ad5216ede8 chore: Add checks to fetch-prebuilt script.
This adds a check that if for example wget isn't installed it'll install from brew via brew install.

Signed-off-by: Stern <stern@sidestore.io>
2024-11-08 23:24:46 -05:00
Magesh K
ee9c9cb200 refresh-app: enhanced error logging when app extension validation check fails (#730)
refresh-app: enhanced error logging when app exenstion validation check fails
2024-11-04 04:05:13 -05:00
June Park
0fa2407e36 Merge pull request #729 from mahee96/sidestore-crash-xcode
xcode: Debug dylib support is still broken in iOS 18 causing "No Entry Point Found. checked(null)"
2024-11-04 17:39:05 +09:00
Magesh K
11dba4dd94 Fix-regression: restored update-check logic(4bcb9e1) in InstalledApp.swift which was dropped in (4989c42) (#728) 2024-11-04 03:38:28 -05:00
Magesh K
916e507ba0 xcode: Debug dylib is still broken in iOS 18 causing "No Entry Point Found. checked(null)" 2024-11-04 12:53:30 +05:30
Magesh K
ef1edab045 Fix for Refresh Operation causing renewal/install of provisioning profiles for removed app extensions (#727)
* Fix AppExtensions not being updated on Disk after db is updated in InstallAppOperation

* refresh-Extensions: Added check to ensure extensions in DB and DISK matches if not then cancel current refresh request
2024-11-03 17:30:39 -05:00
ny
70b7d1319e More attempts at fixing app limit issues 2024-10-24 00:41:29 -04:00
ny
9906a4cdaa Attempt to make the bypass more sane 2024-10-23 07:31:52 -04:00
ny
64b23b77a8 Clean/fix this implementation 2024-10-23 06:44:39 -04:00
ny
ddd27357d8 Fix detection for SparseRestore patch 2024-10-23 06:07:58 -04:00
Moonsn
ac4c30569b feat: show custom anisette server list if set (#717) 2024-10-22 20:16:37 -04:00
nythepegasus
c9040f46ad Actually fix deprecation warnings 2024-10-17 06:14:44 -04:00
nythepegasus
bc8c9f6b90 Update GH Actions to fix deprecation warnings 2024-10-17 05:58:38 -04:00
nythepegasus
d90b84bb03 Try to fix refreshing and removing extensions
Silly fix

Signed-off-by: nythepegasus <me@nythepegas.us>
2024-10-17 05:18:10 -04:00
nythepegasus
e032b82a30 Remove this
Should fix remove app extensions

Signed-off-by: nythepegasus <me@nythepegas.us>
2024-10-17 04:47:42 -04:00
ny
c669fd406e Add a few more checks for app limits 2024-10-11 02:50:48 -04:00
ny
62c1bd4b55 Add a max limit 2024-10-11 02:33:25 -04:00
ny
e205cb3c91 Fix these actions to point correctly 2024-10-11 02:24:36 -04:00
ny
2c44aa3cdd Fix up this so it actually sets the value 2024-10-11 01:48:01 -04:00
ny
8c3da70a7d Add bad app limit toggle implementation 2024-10-11 01:12:22 -04:00
Stern
a0775bbdc9 (fix:) Update Patreon Secret and links
Signed-off-by: Stern <stern@sidestore.io>
2024-09-16 07:18:41 -04:00
nythepegasus
72910aa360 [skipci] Bump to 0.5.9 for new nightly cycle
Signed-off-by: nythepegasus <me@nythepegas.us>
2024-09-07 14:44:14 -04:00
polymo1
b85ff49276 legal: developer's certificate of origin 1.1 (#686) 2024-09-07 14:23:16 -04:00
nythepegasus
4f2e8ef066 Attempt to fix sharing ipa files
Yeah, yeah, I shouldn't commit to develop, I have a hunch though

Signed-off-by: nythepegasus <me@nythepegas.us>
2024-09-02 08:19:28 -04:00
June Park
5156d182c1 [skip ci] Update bug_report.yml
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-08-20 17:53:50 +09:00
June
fab85ad24a add entitlements 2024-08-19 11:14:42 +09:00
June Park
5cf06261db Selective app extension removal (#677) 2024-08-16 21:28:42 -04:00
June Park
877410206f [no ci] Update pr.yml to cache builds
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-08-17 10:14:04 +09:00
June
42bd4e24f0 Make app extension popup less annoying by not showing when refreshing (it doesn't do anything anyway) 2024-08-17 00:16:55 +09:00
June Park
40daafa17a Remove cache clear
We don't need to do this anymore

Signed-off-by: June Park <me@pythonplayer123.dev>
2024-08-16 15:41:02 +09:00
June
a9d7ca22f9 Fix certificate issues 2024-08-16 15:34:57 +09:00
June Park
0bc6167739 [skip ci] add caches to stable.yml
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-08-16 14:32:25 +09:00
June Park
005faab0fb [skip ci] add caches to beta
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-08-16 14:30:47 +09:00
June Park
9f5aa948db Make caches not have workflow name
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-08-16 14:30:13 +09:00
nythepegasus
53ac549b22 Bump SideStore Version to 0.5.8
For this current nightly track of commits until we "officially" release it

Signed-off-by: nythepegasus <me@nythepegas.us>
2024-08-16 01:22:58 -04:00
J. Laymon
6cef79e6c4 Better pairing file info (#676)
makes the pop up give you a button to press for help rather than giving a plaintext url to a page that doesn't exist
2024-08-16 01:20:53 -04:00
June P
63d773f327 [build] need to see if caching works 2024-08-16 13:40:04 +09:00
June
1ee430e55e Increase runtime performance 2024-08-16 13:32:35 +09:00
June Park
69504b7b73 Add incremental builds
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-08-16 13:17:07 +09:00
June
42e7ac786c Make app extensions optional across the board 2024-08-16 12:58:06 +09:00
June Park
0f61375b66 Delete AltStore.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-08-15 22:13:19 +09:00
June
87be81ac7e more descriptive machine names for generated certs 2024-08-15 22:09:16 +09:00
June Park
4da33deeef Makes revoking optional (#675) 2024-08-14 20:08:37 -07:00
June Park
616319c2fd Add nightly.link description
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-08-15 12:00:39 +09:00
June
54acf5b745 More descriptive errors 2024-08-15 11:21:50 +09:00
stossy11
bf1ed681fc Fix SideJITServer Support for iOS 17+ (#674)
* FIx SideJITServer Support

* Fix SideJITServer Address

* Add Warning when Overwriting SideJITServer Address

* Fix Optional Value for SideJITServer URL

* Update SideJITServer Address Overwriting

* Fix Enabling JIT and Fix IP Address Loop

* Fix No WiFi or VPN! error when using SideJITServer
2024-08-14 16:58:26 -07:00
June Park
f32997d3dc Make it so we don't flood altstore
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-08-14 21:34:22 +09:00
June Park
74a0085b55 Add nightly.link support
Pushing directly as this shouldn't mess anything other than CI up

Signed-off-by: June Park <me@pythonplayer123.dev>
2024-08-14 21:14:27 +09:00
Stern
3d98896074 Merge pull request #673 from 0-Blu/develop
Added more feedback options.
2024-08-13 14:48:17 -04:00
Stephen
a2b9912085 Update SettingsViewController.swift
Signed-off-by: Stephen <158498287+0-Blu@users.noreply.github.com>
2024-08-13 14:15:31 -04:00
Stephen
96f2f7cd95 Update SettingsViewController.swift
Signed-off-by: Stephen <158498287+0-Blu@users.noreply.github.com>
2024-08-12 22:30:15 -04:00
Stephen
63cec3e0a8 Added more feedback options.
Signed-off-by: Stephen <158498287+0-Blu@users.noreply.github.com>
2024-08-12 19:21:59 -07:00
Stern
a0b37dab77 Merge pull request #672 from 0-Blu/develop
UI Improvements for the Anisette Servers View.
2024-08-12 20:46:44 -04:00
Stephen
0ee46c2f61 Update AnisetteServerList.swift
Signed-off-by: Stephen <158498287+0-Blu@users.noreply.github.com>
2024-08-12 20:31:13 -04:00
Stephen
7676f58021 Fixed Dark Mode for AnisetteServerList.swift
Signed-off-by: Stephen <158498287+0-Blu@users.noreply.github.com>
2024-08-12 16:53:45 -04:00
Stephen
28ca7cd7a6 Better AnisetteServerList.swift (NEEDS TESTING!!!!!!)
Signed-off-by: Stephen <158498287+0-Blu@users.noreply.github.com>
2024-08-12 15:14:57 -04:00
nythepegasus
49acd5684c Bump SideStore version to 0.5.7
Signed-off-by: nythepegasus <nythepegasus84@gmail.com>
2024-08-05 21:44:59 -04:00
June Park
4989c42d1e merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration
* Change error from Swift.Error to NSError

* Adds ResultOperation.localizedFailure

* Finish Riley's monster commit

f59b5902ff8c76d4eb0ff806794ffebe635844b5
May the Gods have mercy on my soul.

* Fix format strings I broke

* Include "Enable JIT" errors in Error Log

* Fix minimuxer status checking

* [skip ci] Update the no wifi message to include VPN

* Opens Error Log when tapping ToastView

* Fixes Error Log context menu covering cell content

* Fixes Error Log context menu appearing while scrolling

* Fixes incorrect Search FAQ URL

* Fix Error Log showing UIAlertController on iOS 14+

* Fix Error Log not showing UIAlertController on iOS <=13

* Fix wrong color in AuthenticationViewController

* Fix typo

* Fixes logging non-AltServerErrors as AltServerError.underlyingError

* Limits quitting other AltStore/SideStore processes to database migrations

* Skips logging cancelled errors

* Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion

We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors.

However, we kept the underlying Core Data property name the same to avoid extra migration.

* Conforms OperatingSystemVersion to Comparable

* Parses AppVersion.minOSVersion/maxOSVersion from source JSON

* Supports non-NSManagedObjects for @Managed properties

This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before.

* Supports optional @Managed properties

* Conforms AppVersion to AppProtocol

* Verifies min/max OS version before downloading app + asks user to download older app version if necessary

* Improves error message when file does not exist at AppVersion.downloadURL

* Removes unnecessary StoreApp convenience properties

* Removes unnecessary StoreApp convenience properties as well as fix other issues

* Remove Settings bundle, add SwiftUI view instead

Fix refresh all shortcut intent

* Update AuthenticationOperation.swift

Signed-off-by: June Park <rjp2030@outlook.com>

* Fix build issues given by develop

* Add availability check to fix CI build(?)

* If it's gonna be that way...

---------

Signed-off-by: June Park <rjp2030@outlook.com>
Co-authored-by: nythepegasus <nythepegasus84@gmail.com>
Co-authored-by: Riley Testut <riley@rileytestut.com>
Co-authored-by: ny <me@nythepegas.us>
2024-08-05 21:43:52 -04:00
Stern
b87ca969d1 Merge pull request #658 from therealFoxster/develop
Rename "AltStore" to "SideStore"
2024-07-19 18:50:32 -04:00
Foxster
d5d16ee09b Rename "AltStore" to "SideStore" 2024-07-17 16:21:39 -07:00
bogotesr
94bdacaae8 Revert "improve UX on intro popup"
This reverts commit d81eb55c0a.
2024-07-12 02:40:26 -07:00
bogotesr
d81eb55c0a improve UX on intro popup
fixes that the url for the pairing file info was out of date

adds button for taking user to that page rather than just having a url in text
2024-07-12 02:37:06 -07:00
polymo1
02d1800959 add(readme): discord badge 2024-07-07 20:07:36 -04:00
stossy11
8f65c8a302 Add SideJITServer Support for Enabling JIT on iOS 17+ in app (#630) 2024-06-16 16:43:25 -07:00
Stern
c0d28fa022 Merge pull request #612 from stossy11/MyAnisette
Add Stossy11 Anisette Server
2024-04-29 09:03:05 -04:00
stossy11
a26ca4ddbb Add Stossy11 Anisette Server 2024-04-29 23:01:07 +10:00
nythepegasus
01c9d49c7f Actually fix embedded pairing file issue 2024-04-23 03:20:40 -04:00
Stern
430bdc87cf Merge pull request #604 from wesbryiecom/develop 2024-04-14 20:55:56 -07:00
Wes Bryie
b0d92b3a1e Add ani.wesbryie.com to Anisette list
Signed-off-by: Wes Bryie <wes@wesbryie.com>
2024-04-14 23:43:04 -04:00
Spidy123222
f144fe61cd Remove patreon exclusivity message for sources (#594) 2024-03-14 01:39:40 -07:00
nythepegasus
5674c1451d Remove outdated/down servers for now
Eventually this could be dynamically pulled/inside SideStore
but extra DNS entries and cleaning can work for now
2024-02-23 20:11:00 -05:00
nythepegasus
9467b1cb6c Fix pairing file not resetting when embedded 2024-02-23 19:46:31 -05:00
nythepegasus
2ce0791529 [skip ci] Fixed typo
Signed-off-by: nythepegasus <nythepegasus84@gmail.com>
2024-02-16 13:55:33 -05:00
Stern
baf982493c [skip ci] Adds ThatStella7922's Trusted Source
Signed-off-by: Stern <70122891+SternXD@users.noreply.github.com>
2024-02-16 13:44:37 -05:00
June P
a3bd723db6 bugfix(launch):fix analytics notice 2024-01-30 10:52:22 +09:00
June Park
7126091442 Update stable.yml
Signed-off-by: June Park <rjp2030@outlook.com>
2024-01-30 10:40:34 +09:00
June Park
cf22c8fed0 ouch pushed selfhosted code
Signed-off-by: June Park <rjp2030@outlook.com>
2024-01-30 10:40:07 +09:00
June P
3d5692ef81 feat(launch):add analytics notice 2024-01-30 10:04:33 +09:00
June Park
9432c07b8d Update nightly.yml
Signed-off-by: June Park <rjp2030@outlook.com>
2024-01-25 23:28:58 +09:00
June P
c4dab645c9 change to self-hosted 2024-01-25 23:18:28 +09:00
naturecodevoid
09b82a8cc4 [skip ci] Remove me from feature report assignees
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-12-12 18:32:21 -08:00
naturecodevoid
c5f47dabc1 [skip ci] Remove me from bug report assignees
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-12-12 18:32:05 -08:00
Stern
ff7d018d54 [skip ci] ci: Update stable.yml
This removes workflow_dispatch as it was making the builds show as SideStore `develop`

Signed-off-by: Stern <xsternent@gmail.com>
2023-11-28 05:50:44 -05:00
Stern
6d8ed2b608 Merge pull request #550 from SideStore/Add-description-for-idle
Add description on what disable idle timeout toggle + change add to Siri text for accuracy
2023-11-28 03:12:22 -05:00
Spidy123222
8c4010d558 Change button text for adding Siri to refresh apps
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-11-27 23:39:20 -08:00
Spidy123222
82d3011fc0 add description on what disable idle timeout toggle
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-11-27 23:18:36 -08:00
junepark678
7dbd5022ed bugfix(NoIdle): Fix slider not being set to correct value on load 2023-11-28 12:00:20 +09:00
June Park
221dfae820 [skip ci] bump nightly to 0.5.5
Signed-off-by: June Park <rjp2030@outlook.com>
2023-11-28 03:02:04 +09:00
June P
b8f0d3e650 bugfix: fix removal of attributes 2023-11-28 02:34:15 +09:00
Stern
27023125b7 Merge pull request #539 from junepark678/NoIdle
feat(Operations): don't idle timeout during installations
2023-11-27 12:12:10 -05:00
June P
e3765e5504 bugfix: fix appending to a list that is nil 2023-11-28 02:04:14 +09:00
June Park
3b4a658464 Merge branch 'develop' into NoIdle
Signed-off-by: June Park <rjp2030@outlook.com>
2023-11-28 00:46:48 +09:00
junepark678
a653e1beb5 bugfix: make it able to be toggled, fix bug in crash on installation 2023-11-28 00:44:47 +09:00
nythepegasus
2f38b687f4 [skip ci] Bump nightly to 0.5.4
Signed-off-by: nythepegasus <nythepegasus84@gmail.com>
2023-11-27 10:39:41 -05:00
Stern
3243b4358c [skip ci] ci: add workflow_dispatch
Signed-off-by: Stern <xsternent@gmail.com>
2023-11-27 10:18:06 -05:00
nythepegasus
cc59e5ab93 [skip ci] Add other project maintainers to CODEOWNERS
Signed-off-by: nythepegasus <nythepegasus84@gmail.com>
2023-11-27 09:37:25 -05:00
junepark678
81806ca4ec chore(Clear Cache): do proper error handling 2023-11-27 09:31:36 -05:00
June Park
c82ad2486b bugfix(settings): fix rounding issues on clear cache button (#536) 2023-11-27 09:31:36 -05:00
Spidy123222
a9b54c2f70 move debug row 2023-11-27 09:31:36 -05:00
Spidy123222
cc21c45238 make button function again 2023-11-27 09:31:36 -05:00
Spidy123222
127d8b1d08 change order of settings debug section 2023-11-27 09:31:36 -05:00
Spidy123222
2405fabca3 Add button to storyboard 🙄 2023-11-27 09:31:36 -05:00
Spidy123222
8c864a3959 please fix to show button 2023-11-27 09:31:36 -05:00
Spidy123222
7418f8177d attempt fix settings 2023-11-27 09:31:36 -05:00
Spidy123222
59f4629cfc fix the mighty error 2023-11-27 09:31:36 -05:00
Spidy123222
74e604eb3e get rest of batcherror 2023-11-27 09:31:36 -05:00
Spidy123222
34b906f722 please o riley build 2023-11-27 09:31:36 -05:00
Spidy123222
1aff19cf86 hopefully fix error code build error 2023-11-27 09:31:36 -05:00
Riley Testut
15f699a5c7 Adds “Clear Cache” description to Techy Things section footer
(cherry picked from commit fe0e637135ed2c64e9fe3fb65a82930369e907e3)
2023-11-27 09:31:36 -05:00
Riley Testut
78e4f52b67 Adds “Clear Cache” button to remove temporary files and uninstalled app backups
(cherry picked from commit 4ff61d33f6b31c27cb61de68767a5151764feb96)
2023-11-27 09:31:36 -05:00
junepark678
95704a0537 chore(App IDs, My Apps): change back to full 2023-11-27 09:22:19 -05:00
junepark678
cdafd03aa4 chore(App IDs, My Apps): change to use DateComponentsFormatter.UnitsStyle.abbreviated 2023-11-27 09:22:19 -05:00
junepark678
190a05ec6b bugfix(App IDs, My Apps): fix date display 2023-11-27 09:22:19 -05:00
junepark678
87f0c7d661 bugfix(App IDs, My Apps): display only necessary information 2023-11-27 09:22:19 -05:00
junepark678
0ef10b5d93 bugfix(App IDs, My Apps): calculate in correct direction in time (we aren't time travelers) 2023-11-27 09:22:19 -05:00
junepark678
3d23b09529 feat(My Apps): make expiration dates more specific 2023-11-27 09:22:19 -05:00
junepark678
3b7e8ec0fa chore(App IDs): localize Unknown string 2023-11-27 09:22:19 -05:00
junepark678
7cbc1dff65 feat(App IDs): make expiration dates more specific 2023-11-27 09:22:19 -05:00
junepark678
cb5afe1c8d feat: remove reliance on Info.plist for getting udid 2023-11-27 09:21:54 -05:00
junepark678
f96c3ce350 feat(Operations): don't idle timeout during installations 2023-11-26 10:51:33 +09:00
nythepegasus
43e5dc5950 [skip ci] Update Ignited source URL 2023-11-06 09:02:49 -05:00
Spidy123222
30492bb279 Change pairing file link with new wiki link
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-10-23 13:12:23 -07:00
nythepegasus
5ef38b9f5d Fix AltWidget App Group issue 2023-10-23 10:12:09 -04:00
naturecodevoid
686d4cbab1 Revert most of xcodeproj changes 2023-10-21 20:44:49 -07:00
naturecodevoid
85c440e40b Revert "Attempt to fix submodule dependencies for GH runner"
This reverts commit bac820bcaa.
2023-10-21 20:30:32 -07:00
naturecodevoid
4a2679986a Use forked libplist 2023-10-21 20:30:23 -07:00
nythepegasus
535fe70e50 [skip ci] Bump nightly to 0.5.3
Signed-off-by: nythepegasus <nythepegasus84@gmail.com>
2023-10-21 21:02:41 -04:00
nythepegasus
32eec5bea0 Update Roxas 2023-10-21 20:35:32 -04:00
nythepegasus
bac820bcaa Attempt to fix submodule dependencies for GH runner 2023-10-21 20:34:44 -04:00
nythepegasus
f19129bf0f Update limd/libplist submodules 2023-10-20 22:04:47 -04:00
nythepegasus
b38f0a1896 Add iOS 17 JIT error notice with other errors 2023-10-20 21:51:24 -04:00
nythepegasus
90e5142bcd Hardcode SideStore's URL scheme for now 2023-10-20 21:51:24 -04:00
nythepegasus
d15abb78b2 Revert this change 2023-10-20 21:51:24 -04:00
nythepegasus
cf35e400a8 Add newly compiled AltBackup 2023-10-20 21:51:24 -04:00
Bogdan Seniuc
70a1e5d97a Use provisioning profile details instead of guessing active app limit 2023-10-20 21:50:50 -04:00
Spidy123222
da890f1232 [skip ci] Remove emuthreeds from trusted sources 2023-10-18 21:44:55 -07:00
nythepegasus
4cf699ca67 Change this to be hardcoded SideStore search 2023-10-17 17:38:09 -04:00
nythepegasus
db1accdedd Remove buggy retry code finally 2023-10-17 17:37:29 -04:00
naturecodevoid
cdc3e1f588 Bump version to 0.5.2 so nightly builds have a higher SemVer version than stable 2023-09-18 16:18:05 -07:00
naturecodevoid
661f88a566 Build nightly with latest minimuxer changes to attempt to fix plist_from_memory crash 2023-09-18 16:17:14 -07:00
naturecodevoid
f58132fd21 0.5.1 2023-09-17 14:01:13 -07:00
Spidy123222
efcfe13de9 Change version to 0.5.2 2023-09-17 12:36:44 -07:00
naturecodevoid
c7794dc7b9 Update Swift Packages and submodules (#469)
* Update Swift Packages

* Update submodules

* make sure it builds

---------

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-09-17 10:45:55 -07:00
naturecodevoid
bffaecf2c0 [skip ci] Add more information to staging errors (#468)
* Point to my forks and attempt to add more information to staging errors

* Improve error message a bit

* Revert fetch-prebuilt.sh changes

* Undo some whitespace changes

* missed one

* oops

* [skip ci]

* [skip ci]

* [skip ci] remove staging directory from install app error

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

---------

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
Co-authored-by: Dadoum <dadoum@protonmail.com>
2023-09-17 10:37:49 -07:00
Spidy123222
f4c9d623d4 Update Altsign (August 6) (#467)
This changes the git commit or be our latest altsign version.
2023-09-16 01:51:35 -07:00
nythepegasus
d3951f63e8 Update emuplace source
Signed-off-by: nythepegasus <nythepegasus84@gmail.com>
2023-08-13 11:54:00 -04:00
Spidy123222
00d9639394 Add ignited source
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-08-06 14:15:51 -07:00
Joelle Stickney
6721486fdf Removed Quantum Source
Signed-off-by: Joelle Stickney <joellestickney+commit@gmail.com>
2023-08-06 16:26:24 -04:00
Joelle Stickney
ac3011235a Added Quantum Source to trusted sources
Signed-off-by: Joelle Stickney <joellestickney+commit@gmail.com>
2023-08-06 15:30:21 -04:00
nythepegasus
17c6d7d894 [skip ci] Change Discord custom invite to static website invite
Signed-off-by: nythepegasus <nythepegasus84@gmail.com>
2023-08-04 13:17:02 -04:00
Spidy123222
52a951a279 Fix message and put in proper spot.
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-07-27 23:42:32 -07:00
Spidy123222
f903ee23e1 Make Notification explanation smaller for refresh
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-07-27 23:17:58 -07:00
Nythepegasus
94858e12fa Bump version to 0.5.0 2023-07-27 06:56:51 -04:00
Nythepegasus
d173ddd0ce Update default anisette server 2023-07-27 06:55:31 -04:00
Nythepegasus
33082b676f Fix going to the home screen 2023-07-27 06:23:26 -04:00
Nythepegasus
35f888d95b Reintroduce notification/pop-up 2023-07-27 06:23:26 -04:00
Nythepegasus
9ab744d193 Remove extra returns and make sure to decrement 2023-07-27 06:23:26 -04:00
Nythepegasus
03b0486186 Finally fix retries for minimuxer calls 2023-07-27 06:23:26 -04:00
nythepegasus
a0caecb295 [skip ci] Merge pull request #419 from Nythepegasus/chore/up-fixes
[Chore] Pull upstream changes from AltStore
2023-07-27 06:22:54 -04:00
nythepegasus
4d5c74c012 Merge pull request #414 from SideStore/patch/fix-widget
[Patch] Change Widget entitlements to search hardcoded group first
2023-07-27 06:11:48 -04:00
Riley Testut
f944ae2c79 Enforces 77x31 minimum size for PillButton 2023-07-27 04:47:44 -04:00
Riley Testut
382f430d9e Fixes incorrect cell height for some News items
We need to take layoutMargins into account when calculating the height of the prototype cell.
2023-07-27 04:47:44 -04:00
Riley Testut
ea14ad0fbc Fixes UIDocumentPickerViewController deprecation warnings 2023-07-27 04:47:44 -04:00
Riley Testut
3fc4e33ad8 Fixes “variable mutated after capture by sendable closure” warning 2023-07-27 04:47:44 -04:00
Riley Testut
3c4308f8a6 Fixes AppViewController deprecation warnings 2023-07-27 04:47:43 -04:00
Riley Testut
4cd6284779 Fixes CollapsingTextView “TextKit 1 compatibility mode” runtime warning 2023-07-27 04:47:38 -04:00
Nythepegasus
9ee57e70dc Change circle and logo to just be circle for now 2023-07-27 02:59:30 -04:00
nythepegasus
3342bc069b Change the lock screen icon to be legible
Signed-off-by: nythepegasus <me@nythepegas.us>
2023-07-27 02:25:09 -04:00
Nythepegasus
40089fa5a1 Merge branch 'develop' into patch/fix-widget 2023-07-24 09:29:03 -04:00
Nythepegasus
10f211078d Revert the retries here as these seem buggier 2023-07-24 09:22:34 -04:00
Nythepegasus
def08a8ba4 Remove unused Riley and Shane images from bundle 2023-07-24 07:54:20 -04:00
Nythepegasus
9ad0be7d45 Update AltWidget assets 2023-07-24 07:18:42 -04:00
Nythepegasus
0251df7592 Move the first app group to be known shared group 2023-07-24 05:49:36 -04:00
Nythepegasus
9576cdb052 Update AltWidget/ReleaseEntitlements.plist
Signed-off-by: Nythepegasus <me@nythepegas.us>
2023-07-24 05:40:51 -04:00
Nythepegasus
787eab890e Fix Actions builds for AltWidget 2023-07-24 05:28:26 -04:00
Wes Bryie
d8ad88de8c [skip ci] em_proxy link update (#412)
Signed-off-by: Wes Bryie <wes@wesbryie.tech>
2023-07-21 20:04:44 -07:00
Riley Testut
14df1e0b18 Fixes updating apps with manually-removed app extensions (e.g. uYou+) 2023-07-17 12:15:04 -04:00
Nythepegasus
ec062a8b2e Revert "Fix building on Xcode 15"
This reverts commit 4be65f2608.
2023-07-11 02:00:49 -04:00
Nythepegasus
e204512ddd Go to home screen instead of Safari/notif combo 2023-07-11 01:45:14 -04:00
Nythepegasus
0c891cf3b5 Add various retries to the minimuxer calls 2023-07-11 01:44:11 -04:00
Nythepegasus
0b0ad8b925 Xcode 15 keeps adding imobiledevice.swift 2023-07-10 14:33:53 -04:00
Nythepegasus
22986f31ec These vars don't change, let's use let keyword 2023-07-10 14:32:41 -04:00
Nythepegasus
f0fbdb0008 Update AltSign and OpenSSL packages 2023-07-09 14:00:02 -04:00
Nythepegasus
8b67727000 Fix libfragmentzip's search path 2023-07-09 14:00:02 -04:00
Nythepegasus
4be65f2608 Fix building on Xcode 15 2023-07-09 13:59:55 -04:00
Spidy123222
df4d32f4b3 [skip ci] use odyssey team unified source
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-07-08 23:53:53 -07:00
Spidy123222
37b89bbdcc [skip ci] Add Taurine
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-07-08 23:32:41 -07:00
Nythepegasus
e73d266d80 Update Macley omnisette server IP
Signed-off-by: Nythepegasus <nythepegasus84@gmail.com>
2023-07-03 10:47:54 -04:00
Joshua Laymon
1192bac4e6 Merge pull request #387 from bogotesr/develop
Fix up patreon screen and add socials
2023-06-21 21:58:04 -07:00
Spidy123222
66d12e5902 Replace old servers with macley v3 ansiette 2023-06-14 13:56:12 -07:00
bogotesr
9c3dc1c745 Change Patreon screen again
Refresh to "support us" and include social media.
2023-06-13 23:14:01 -07:00
Stern
f50ce575eb Update pr.yml
Signed-off-by: Stern <xsternent@gmail.com>
2023-06-13 20:31:37 -04:00
Stern
7922442426 Update pr.yml
Signed-off-by: Stern <xsternent@gmail.com>
2023-06-13 20:30:41 -04:00
Spidy123222
91c13db9e4 Add EmuPalace Source 2023-06-12 20:35:36 -07:00
Spidy123222
69ddb00ae2 Upload config (#383)
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-06-12 09:39:20 -07:00
Spidy123222
e93bf77d4f Change label to 0.4.0
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-05-29 19:33:38 -07:00
Spidy123222
24daa141ce Change default anisette to v3 anisette server (#367) 2023-05-29 19:15:15 -07:00
Joelle Stickney
2d51c26b60 Merge pull request #365 from lonkelle/develop
Co-authored-by: Joe Mattiello <mail@joemattiello.com>
2023-05-24 23:11:22 -04:00
Joelle Stickney
f7e6177628 Co-authoring all the things
Co-authored-by: Joe Mattiello <mail@joemattiello.com>
2023-05-24 23:08:33 -04:00
Joelle Stickney
494ce9b997 Update README.md
Co-authored-by: Joe Mattiello <mail@joemattiello.com>
2023-05-24 23:04:45 -04:00
Joelle Stickney
200bfdaa5a Update README.md
Co-authored-by: Joe Mattiello <mail@joemattiello.com>

Signed-off-by: Joelle Stickney <joellestickney+commit@gmail.com>
2023-05-24 03:56:58 -04:00
Joelle Stickney
4530b5ab80 Update README.md
Co-authored-by: JoeMatt <mail@joemattiello.com>

Signed-off-by: Joelle Stickney <joellestickney+commit@gmail.com>
2023-05-24 03:55:28 -04:00
Joelle Stickney
4629f9a886 Update README.md
Signed-off-by: Joelle Stickney <joellestickney+commit@gmail.com>
2023-05-24 03:53:51 -04:00
naturecodevoid
c05a28d867 Anisette V3 (#324)
* initial anisette V3 implementation

* update V3 urls and log version

* fix crash where FetchAnisetteDataOperation.clientInfo would be nil when getting anisette V3 without provisioning first

* move adi.pb reset to its own button instead of doing it on sign out

* fallback to V1 if client_info fails

* make sure to unwrap optional strings

* feat(anisette): update v3 usage, improve error messages and names, report v3 errors to the user

* refactor(anisette): reduce duplicate JSON to anisette code

* fixes(anisette v3): improve errors, fix v3 server check, fix some edge cases where SideStore could crash and instead return an error, retry on -45061
2023-05-18 01:30:18 -07:00
SoY0ung
13d14352b1 Fix 'The name for this app is invalid' error(#361)
Fix 'The name for this app is invalid' error when sideloading with non-ascii name ipa
2023-05-15 12:38:26 +08:00
SoY0ung
4d707e8b88 Optimizing function calls
Thanks for @ktprograms advice
2023-05-14 19:06:22 +08:00
SoY0ung
9d176c1fae Fix 'The name for this app is invalid' error
This error is related to App ID creation failure.
App ID name must be an ascii text. It is not allowed to create an App ID with non-ascii text like Chinese, Japanese.
If the name is NOT an ascii text, using bundleID instead.
2023-05-14 02:55:36 +08:00
naturecodevoid
1d5c2a6105 fix: open Safari instead of force closing and provide a fallback for users with notifications disabled 2023-05-06 19:25:37 -07:00
naturecodevoid
b0a24b7dc7 fix: ensure minimuxer is started when refreshing in the background 2023-04-16 10:07:04 -07:00
naturecodevoid
387fce527a fix: use a notification instead of an alert for force close 2023-04-16 09:29:12 -07:00
naturecodevoid
f90179eaa0 fix: removing _CodeSignature folder before resigning 2023-04-13 21:21:51 -07:00
naturecodevoid
8190198d14 [skip ci] ci: fully rename SideStore.ipa, even after extracting the artifact zip 2023-04-13 07:30:20 -07:00
naturecodevoid
d588667b26 fix: force close SideStore after 3 seconds if still reinstalling 2023-04-13 07:20:36 -07:00
naturecodevoid
0bdd3fe06a fix: hopefully reduce ApplicationVerificationFailed errors by removing _CodeSignature folders since those may cause a problem 2023-04-12 19:53:27 -07:00
naturecodevoid
1bd275779f fix: add .AltWidget to app group ID when modifying for SideStore 2023-04-12 07:46:14 -07:00
naturecodevoid
b88c00bef6 fix: always reinstall when refreshing ourselves 2023-04-11 21:50:15 -07:00
naturecodevoid
c23ddb697e Revert "Don't reinstall on first SideStore refresh"
This reverts commit b825d469a9.
2023-04-11 21:12:01 -07:00
naturecodevoid
a7dcfaa1bb [skip ci] chore: rename tempEnt.plist to ReleaseEntitlements.plist to reduce future confusion 2023-04-11 21:09:32 -07:00
naturecodevoid
c03afe50bd chore: Remove old apps.json/app.json files 2023-04-11 21:05:53 -07:00
naturecodevoid
67646df29f refactor: remove minimuxerToOperationError in favor of extending MinimuxerError to be a LocalizedError and remove unused cases from OperationError 2023-04-11 21:04:07 -07:00
naturecodevoid
b31288a6fb Use XYZ0123456 team ID for tempEnt.plist
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-04-06 20:45:18 -07:00
naturecodevoid
c363ea64c7 Update tempEnt.plist 2023-04-06 12:42:37 -07:00
f1shy-dev
c2c560aacc Update tempEnt.plist to change AltStore to SideStore
Signed-off-by: f1shy-dev <56125930+f1shy-dev@users.noreply.github.com>
2023-04-06 20:23:27 +01:00
naturecodevoid
ab82e45c32 [skip ci] Include commit SHA in nightly version 2023-04-02 15:08:48 -07:00
naturecodevoid
508bb2bdd5 [skip ci] Add version to artifact name
we can't do this for releases because some download URLs might rely on it being named SideStore.ipa

Build Info will also have version anyways
2023-04-02 08:01:57 -07:00
naturecodevoid
c278cb4f0c [skip ci] Log version 2023-04-02 08:00:11 -07:00
naturecodevoid
b206d2fe98 [skip ci] Fix PR commit suffix 2023-04-02 07:58:38 -07:00
naturecodevoid
78ce338939 [skip ci] Rename and move the first application groups log 2023-04-02 07:34:48 -07:00
naturecodevoid
7aef2cf2b5 Remove app groups that contain AltStore 2023-04-01 20:03:15 -07:00
naturecodevoid
a2e873e1e1 Revert Joelle's fix 2023-04-01 16:15:04 -07:00
naturecodevoid
4fa3ee12ae Log provisioning profile application groups 2023-04-01 16:10:40 -07:00
naturecodevoid
2d0d967136 Reduce duplicate consts 2023-04-01 16:10:05 -07:00
naturecodevoid
b825d469a9 Don't reinstall on first SideStore refresh 2023-04-01 16:09:28 -07:00
naturecodevoid
5b96e3297a refactor minimuxer to use swift-bridge (#321)
also add team ID to the end of the bundle ID for Debug builds to mirror SideServer
2023-04-01 16:02:12 -07:00
Joelle Stickney
c2b7b079d7 Fixes widget refreshing and is more thorough matching store ID 2023-03-28 23:48:24 -04:00
Joelle Stickney
78eb2ed5c6 Update store check to check for AltServer or SideServer installation 2023-03-28 01:33:55 -04:00
naturecodevoid
d6073f874c [skip ci] include commit SHA in PR builds
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-03-24 08:56:30 -07:00
Riley Testut
93b6a0f984 Fixes “no provisioning profile with the requested identifier…” error
As of March 20, 2023, deleting an app’s auto-generated free provisioning profile is no longer supported. However, fetching the provisioning profile now re-generates is every time, so there’s no need to delete it first.

As a workaround, we now simply use the first profile we fetched if we receive an error when deleting it. This approach should continue to work even if Apple later reverses this change.
2023-03-21 18:52:56 -04:00
Joe Mattiello
577fcf7568 Update README.md
Signed-off-by: Joe Mattiello <mail@joemattiello.com>
2023-03-20 00:02:11 -04:00
Joe Mattiello
31a63096fc Update README.md
Signed-off-by: Joe Mattiello <mail@joemattiello.com>
2023-03-20 00:00:56 -04:00
Joe Mattiello
d5491337cb Update README.md
Add repobeats svg

Signed-off-by: Joe Mattiello <mail@joemattiello.com>
2023-03-19 23:28:32 -04:00
naturecodevoid
83c8513090 [skip ci] use bundle ID from Build.xcconfig in AltStore.xcconfig 2023-03-12 16:38:59 -07:00
naturecodevoid
d7b8a3d647 [skip ci] rename the *.dSYM artifact so that macOS treats it as a normal folder 2023-03-07 08:24:28 -08:00
naturecodevoid
87b4f58b8b attach debugging symbols to actions builds 2023-03-07 07:50:31 -08:00
naturecodevoid
fc66845fe4 add https for ani.sidestore.io to Settings.bundle 2023-03-07 07:27:36 -08:00
Nythepegasus
11066a955f Merge pull request #288 from SideStore/Remove-jk-anisette 2023-03-07 00:54:51 -05:00
Spidy123222
9eea16a0fb Merge branch 'develop' into Remove-jk-anisette 2023-03-06 12:11:02 -08:00
Spidy123222
56c2d59fd8 Change version to 0.3.2
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-03-06 12:10:39 -08:00
Nythepegasus
8f870c87dd Add SSL encryption to ani.sidestore.io
Signed-off-by: Nythepegasus <nythepegasus84@gmail.com>
2023-03-06 14:09:16 -05:00
Spidy123222
f51e345360 Replace sideloady to use sidestore ani
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-03-05 19:27:25 -08:00
Spidy123222
da08b54162 Remove jkcoxson anisette servers.
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-03-05 13:45:10 -08:00
naturecodevoid
d7aac55554 Update error codes to match minimuxer error codes (this is why people got Unknown error instead of an actual error message)
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-03-04 08:08:35 -08:00
Spidy123222
5581c34301 Change version to 0.3.1
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-03-01 13:17:09 -08:00
naturecodevoid
16a04d2ccd Cherry pick app ID logging fix from duplicate profiles PR 2023-02-25 14:36:37 -08:00
Joe Mattiello
e0a2f88d22 Merge pull request #272 from SideStore/naturecodevoid/prebuild-rust-deps-attempt-2
More actions updates, contributing guide
2023-02-24 00:00:43 -05:00
naturecodevoid
24959bee25 [skip ci] actions: Add info/automate cache resetting 2023-02-21 17:27:56 -08:00
naturecodevoid
4033d4dd55 [skip ci] Makefile: Remove build_rust_dependencies 2023-02-21 17:07:58 -08:00
naturecodevoid
756e6bdfdc Project: update CONTRIBUTING.md to use Makefile 2023-02-21 17:01:24 -08:00
naturecodevoid
b647ba62e7 Revert "modify actions to work on test branch"
This reverts commit cdae15354f.
2023-02-21 12:51:34 -08:00
naturecodevoid
ac3fc718af update release descriptions 2023-02-21 12:42:56 -08:00
naturecodevoid
cdae15354f modify actions to work on test branch 2023-02-21 12:24:25 -08:00
naturecodevoid
15280df99f cleanup actions, revamp beta action, modify nightly build num system to be day specific 2023-02-21 12:23:12 -08:00
naturecodevoid
9c6ef532a0 cleanup makefile and add build steps from github actions 2023-02-21 12:19:08 -08:00
naturecodevoid
ac8ae5206e No more rust 2023-02-20 20:36:39 -08:00
naturecodevoid
bdbb3099d7 fetch-prebuilt.sh whitespace improvement 2023-02-20 19:43:46 -08:00
naturecodevoid
c28f6d0713 Update CONTRIBUTING.md 2023-02-20 18:58:42 -08:00
naturecodevoid
68d2226f7b add changes from attempt #1 2023-02-20 18:50:40 -08:00
naturecodevoid
0d5765abc8 use prebuilt binaries 2023-02-20 18:48:21 -08:00
naturecodevoid
3ff85976df remove submodules 2023-02-20 16:33:11 -08:00
Joss Laymon
0c92feaa6e Use new pojav url
Signed-off-by: Joss Laymon <71040782+bogotesr@users.noreply.github.com>
2023-02-20 11:21:30 -07:00
naturecodevoid
f8405529a8 SourcesViewController: Fix 1 trusted source causing an error making all trusted sources fail to load 2023-02-18 20:33:44 -08:00
Spidy123222
c5abb33776 Replace placeholder video with instructions. (#266) 2023-02-15 21:14:51 -08:00
naturecodevoid
72f3097135 Revamp issue and PR templates (#253)
* Create config.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Delete bug_report.md

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Create bug_report.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Create feature_request.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Create pull_request_template.md

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

---------

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-02-06 00:43:44 -05:00
oqammx86
9f021e559c Add sidestore anisette server
Signed-off-by: oqammx86 <84847714+oq-x@users.noreply.github.com>
2023-02-02 13:05:36 -05:00
naturecodevoid
70900f2e3c Revert "Release channel support (#239)"
This reverts commit 09d11bdff7.
2023-02-02 08:09:15 -08:00
naturecodevoid
09d11bdff7 Release channel support (#239)
* Release channel support

- Show SideStore in Browse if it's not from the current SideStore source
- Change SideStore source URL and source ID based on if beta and nightly are in the version string
- Use StoreApp name for InstalledApp name to allow for source-specified name to show up in My Apps

* My Apps: Fix incorrect app name on first launch

* News: fix duplicate news items from multiple SideStore release channel sources

* Trusted Sources: Add stable and beta
2023-02-02 08:05:27 -08:00
naturecodevoid
50980714a9 Update minimuxer 2023-02-01 20:05:26 -08:00
naturecodevoid
a814b845d8 Add PR suffix to version in PR workflow
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-26 17:13:32 -08:00
naturecodevoid
15a5a9dc3f Update .gitignore 2023-01-25 06:50:06 -08:00
naturecodevoid
96f52b8d45 Merge remote-tracking branch 'upstream/develop' into develop 2023-01-19 17:39:11 -08:00
naturecodevoid
a48bd8e0de Fix build errors 2023-01-19 17:37:43 -08:00
naturecodevoid
d3d82da68f Remove debug
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-19 08:30:47 -08:00
naturecodevoid
35087f4b9e Merge branch 'develop' of https://github.com/naturecodevoid/SideStore into develop 2023-01-19 07:56:52 -08:00
naturecodevoid
3f45d19e6f Merge branch 'develop' of https://github.com/SideStore/SideStore into develop 2023-01-19 07:54:25 -08:00
naturecodevoid
4bcb9e19ae SemVer version comparison 2023-01-19 07:52:47 -08:00
Joelle Stickney
9b523ce1e9 Updated Patreon Link 2023-01-18 14:41:24 -05:00
Joelle Stickney
7369e78b12 Update README.md
Signed-off-by: Joelle Stickney <joellestickney+commit@gmail.com>
2023-01-13 10:39:12 -05:00
Joe Mattiello
b7885db7ba Merge pull request #219 from SoY0ung/file_sharing
Reset Pairing File and checking minimuxer log in app
2023-01-12 20:35:06 -05:00
naturecodevoid
34482afa6c Merge remote-tracking branch 'upstream/develop' into develop 2023-01-09 17:40:50 -08:00
SoY0ung
d9b684284d You can check minimuxer log in Error Log Page 2023-01-09 16:17:00 +08:00
SoY0ung
25640984b7 You can access SideStore document from File App 2023-01-09 15:19:18 +08:00
SoY0ung
44939df828 Add 'Reset Pairing File' 2023-01-09 15:15:31 +08:00
SoY0ung
abd233a54f Fix Advanced Setting display error 2023-01-09 14:13:31 +08:00
jawshoeadan
cb94325da8 Merge pull request #214 from jawshoeadan/remove-hardcoded-me
Update AltBackup.ipa to remove any hardcoded stuff
2023-01-07 18:02:56 -08:00
Jawshoeadan
cf521a92c0 Update AltBackup.ipa 2023-01-04 21:55:00 -08:00
Joe Mattiello
3f2ff64f86 Merge pull request #213 from SideStore/feature/JoeMFixes
Joe M fixes for 1.0
2023-01-04 12:58:56 -05:00
Joseph Mattello
c78a03d70f Add placeholder for minimux retries
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 12:20:08 -05:00
Joseph Mattello
d7df116362 add workflow to attach builds to PR
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:54:29 -05:00
Joseph Mattello
25b7da0677 final classes marked as final
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:52:12 -05:00
Joseph Mattello
42fba53554 allow simulator to launch w/o pairing file
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:32:04 -05:00
Joseph Mattello
2f064fefc6 log functions inlineable
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:31:51 -05:00
Joseph Mattello
aab84c8167 add final class to some classes
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:31:41 -05:00
Joseph Mattello
415eb430ff refs #160 codable feed structs
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:31:28 -05:00
naturecodevoid
30f52b15e0 Revamp actions to have stable, beta and nightly builds (#210)
* start on nightly builds

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update build.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update build.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update build.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update build.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update build.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update build.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Remove testing logic, final changes

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Nightly release build (#2)

* Update and rename build.yml to nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Create stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* trigger on tag

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* add version and build number

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* test

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Revert "test"

This reverts commit 9dff8d1d878a764a432ef4560300acdb4407313a.

* Remove pr from stable

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* add pr.yml

* Add nightly suffix and build number

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* add beta

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* [beta] test

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Remove test

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:55:14 -05:00
naturecodevoid
e01ac1ba27 Nightly release build (#2)
* Update and rename build.yml to nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Create stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* trigger on tag

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* add version and build number

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* test

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Revert "test"

This reverts commit 9dff8d1d878a764a432ef4560300acdb4407313a.

* Remove pr from stable

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* add pr.yml

* Add nightly suffix and build number

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* add beta

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* [beta] test

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Remove test

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
2c46216e00 Remove testing logic, final changes
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
04d47af075 Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
482fdd8297 Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
c9205c0ce7 Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
3259ce0504 Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
6138889c7f Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
8a6ccd7e60 Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
95389d3229 start on nightly builds
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
Joelle Stickney
7ff84ad299 Revert "Change default anisette server"
This reverts commit 58b725d9cb.
2023-01-04 08:52:59 -05:00
Joseph Mattello
2ca9c1c8ef Revert "Merge pull request #189 from Nythepegasus/feature/retries"
This reverts commit 8dd760dfd4, reversing
changes made to 208c86a85c.
2023-01-04 08:48:33 -05:00
Joelle Stickney
e0454e208b Merge pull request #209 from SideStore/feature/deeplink_settings
Open Settings.app from SettingsVC
2023-01-01 12:33:31 -05:00
Joseph Mattello
f9cca57eb8 add button to open Settings.app in SettingsVC
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-01 12:20:08 -05:00
Spidy123222
67f2bbadcb Fix sidestore version
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-12-30 19:07:17 -08:00
Spidy123222
b7c4d0322a Bump version to 0.3.0 (#204)
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-12-30 15:48:15 -08:00
Joe Mattiello
3debee610d Merge pull request #202 from SideStore/pullrequests/jawshoeadan/develop
Merge all of Riley's new error handling stuff REBASED
2022-12-30 17:10:45 -05:00
Joseph Mattello
f74d8c7454 fix 2 missing .c’s in libmobiledevice build
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 17:09:44 -05:00
Joseph Mattello
54229ae3fc fix wrong libframentzip.a link target
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 17:09:44 -05:00
Nythepegasus
4c0607e873 Add a bunch more "logging" throughout signing 2022-12-30 17:09:44 -05:00
Nythepegasus
d94ba84934 Show file extensions to help user choose file 2022-12-30 17:09:44 -05:00
Nythepegasus
ef1c6062f2 Use right Bundle ID for AltWidget 2022-12-30 17:09:44 -05:00
Nythepegasus
9d22b4281e Change the Bundle ID to always be SideStore 2022-12-30 17:09:44 -05:00
Nythepegasus
58b725d9cb Change default anisette server 2022-12-30 17:09:44 -05:00
Nythepegasus
63880dc317 Add our own Analytics key 2022-12-30 17:09:44 -05:00
Nythepegasus
5bfc202cb4 Change version to be 0.1.2 2022-12-30 17:09:43 -05:00
Riley Testut
41287baba3 Displays “TODAY” as section header for logged errors that occured that day 2022-12-30 17:09:15 -05:00
Riley Testut
2f7628ff77 Prevents simultaneous database access from multiple AltStores
AltStore now sends a “WillAccessDatabase” notification before loading the persistent store, which causes other AltStore instances in memory to exit (either immediately, or upon returning to foreground).

This prevents multiple AltStore instances from simultaneously accessing the same database, which could result in corrupted data (especially if they used different database model versions).
2022-12-30 17:09:15 -05:00
Riley Testut
2ea48fb30c Fixes widgets potentially not updating after refreshing apps 2022-12-30 17:09:15 -05:00
Riley Testut
2dbbe85e15 Always displays PatreonViewController loading indicator when fetching patrons
Previously, we only showed the loading indicator if user had not yet cached any Friend Zone patrons.
2022-12-30 17:09:15 -05:00
Riley Testut
796f6e663c Fixes ErrorLogViewController’s dark mode appearance 2022-12-30 17:09:15 -05:00
Riley Testut
2f50dd24f0 [Apps] Updates AltStore beta to 1.6b2 2022-12-30 17:09:14 -05:00
Riley Testut
274a0dac14 Updates LaunchViewController error alert to include more detail
Uses debugDescription over localizedDescription, because that makes it significantly easier to debug the underlying problem from a screenshot.
2022-12-30 17:09:14 -05:00
Riley Testut
4c4e18bb29 Fixes “error migrating persistent store” issue
We now set AppVersion.sourceID during migration, which fixes AppVersion entries conflicting across different Sources if multiple contain the same app + version.
2022-12-30 17:09:14 -05:00
Riley Testut
3c62e51ad2 [Apps] Updates AltStore beta to 1.6b1 2022-12-30 17:09:14 -05:00
Riley Testut
a8e3b31e6b [AltWidget] Adds “icon” style lock screen widget 2022-12-30 17:09:14 -05:00
Riley Testut
03c966b57b [AltWidget] Replaces ProgressRing with SwiftUI.Gauge 2022-12-30 17:09:14 -05:00
Riley Testut
4034b57097 [AltServer] Fixes potential race condition crash when managing connections 2022-12-30 17:09:14 -05:00
Riley Testut
b8e6209913 Updates app version to 1.6b1 2022-12-30 17:09:14 -05:00
Riley Testut
2a81b92408 Resolves AppVersion context-level conflict after migrating from Core Data model v10 2022-12-30 17:09:14 -05:00
Riley Testut
fa1172ba85 Migrates Core Data model from v10 to v11 2022-12-30 17:09:14 -05:00
Riley Testut
fcd2ff51ac Supports new “versions” key in source JSON
Allows sources to list multiple versions of an app.

Preserves backwards compatibility by assigning legacy version values when assigning AppVersions.
2022-12-30 17:09:14 -05:00
Riley Testut
e6f2a50fe4 Adds AppVersion Core Data entity
Preserves redundant fields on StoreApp in database model for backwards compatibility.
2022-12-30 17:09:14 -05:00
Riley Testut
981784c30a Automatically purges LoggedErrors older than one month
Occurs whenever app enters background.
2022-12-30 17:09:14 -05:00
Riley Testut
45b173f089 Adds Error Log screen
Allows users to view a history of all errors that occured when performing app operations.
2022-12-30 17:08:49 -05:00
Riley Testut
719f30ebfb Fixes CollapsingTextView incorrectly showing More button 2022-12-30 17:08:49 -05:00
Riley Testut
d282ba603d Adds LoggedError Core Data entity
Allows us to save certain errors to disk so that they can be viewed again later from an error log.
2022-12-30 17:08:49 -05:00
Riley Testut
95dd752544 Makes AppProtocol.url optional
Allows us to create AnyApp values without a valid file URL.
2022-12-30 17:08:49 -05:00
Joe Mattiello
8dd760dfd4 Merge pull request #189 from Nythepegasus/feature/retries
Add various retries throughout the Rust function calls
2022-12-30 17:04:43 -05:00
Nythepegasus
5d7179a6ac Add retries in the various minimuxer functions 2022-12-30 17:04:19 -05:00
Nythepegasus
fd63eb0547 Retry AFC 10 times before giving up 2022-12-30 17:04:19 -05:00
Joseph Mattello
208c86a85c closes #93 redo of bundle id’s from .app
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 17:03:58 -05:00
Spidy123222
e3d070bac7 Add SideKit integration (#149)
* Add a URL scheme

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Add sidestore as a scheme and fix spacing

* Undo formatting fix

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-12-30 17:03:18 -05:00
Joe Mattiello
95aa9921dc Merge pull request #192 from LitRitt/new-ui-color
Change UI Color
2022-12-30 16:56:54 -05:00
LitRitt
7504a160f4 Changes default app tint color
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-30 15:59:33 -05:00
LitRitt
286a562341 Probably does nothing but changed just in case
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-30 15:59:33 -05:00
LitRitt
7126017538 Changes the color of navigations glyphs and colored text
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-30 15:59:33 -05:00
LitRitt
57388e9e59 Changes settings highlight color
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-30 15:59:33 -05:00
LitRitt
ee1fdf8628 Change settings background color
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-30 15:59:33 -05:00
Joe Mattiello
5357d8348a Merge pull request #142 from SideStore/feature/cargo_in_xcode
Build Rust depends in XCode
2022-12-30 15:59:18 -05:00
Joseph Mattello
85d2fb81bb udpate em_proxy.h
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:47:27 -05:00
Joseph Mattello
4622b40003 fix header paths
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:41:40 -05:00
Joseph Mattello
af2327a580 fix hardcoded paths in generated cargo xcodeprojs
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:28:03 -05:00
Joseph Mattello
981bf7c73a replace fragmentzip.a with xcodeproj
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:23:06 -05:00
Joseph Mattello
6d4c221f99 github action please work
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:17:55 -05:00
Joseph Mattello
f449e46695 fix build for updated submodules
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:17:47 -05:00
Joseph Mattello
2e3ab36286 build.yml updates formatting and xcode ver
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:59:16 -05:00
Joseph Mattello
f67f22951e update libplist to head
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:57:54 -05:00
Joseph Mattello
1aea31ac21 update libmobiledevice to head
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:57:05 -05:00
Joseph Mattello
fb8957fe9a project: fix wrong paths for depends
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:56:52 -05:00
Joseph Mattello
bcb3e636aa cargo: fix github action?
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:49:13 -05:00
Joseph Mattello
f22805b889 Add rust depends to xcode build
Signed-off-by: Joseph Mattello <mail@joemattiello.com>

cargo script for action

fixes path

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

cargo.sh More robust env for xcode cli

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

rust: add xcode projs made with cargo-xcode

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 13:21:57 -05:00
Joelle Stickney
7b5b7d10c7 Merge pull request #201 from SideStore/Add-Community-Store-Trust
Add Community store to Trusted Sources
2022-12-29 01:09:51 -05:00
Spidy123222
f703cb47c4 Update trustedapps.json
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-12-28 23:50:24 -05:00
Joelle Stickney
17d2e8cf3f Merge pull request #200 from SideStore/Add-Yattee-Source
Add Yattee to trusted sources
2022-12-25 05:42:05 -05:00
Spidy123222
61266929d7 Add Yattee to trusted sources
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-12-25 02:22:06 -08:00
Joelle Stickney
d0fdfb5eef Removed Jackson from CODEOWNERS
Due to his two year mission trip and the fact he, as an codeowner, gets added to SideStore issues / PRs automatically, it seemed best to remove him atm.
2022-12-24 18:19:17 -05:00
Joelle Stickney
74dbfa2c98 More credit adjustments 2022-12-24 17:19:55 -05:00
Joelle Stickney
31011deb5a Turn off all background refresh notifications
This removes all background refresh notifications but keeps the "Reminder to open SideStore every so often", and the "x app expires soon" local push notifications.
2022-12-19 23:26:15 -05:00
Joelle Stickney
ccc3fdb9cf Adjusted credits 2022-12-19 07:36:44 -05:00
Joelle Stickney
f858bf705f Credit text changes 2022-12-19 06:40:10 -05:00
f1shy-dev
ee785a0d47 Fix .mobiledevicepairing files (#186)
* Fix .mobiledevicepairing file selection and also add support for .xml pairing files

* Add a more meaningful message for when pairing file selection is cancelled.
2022-12-17 14:13:53 -07:00
LitRitt
f754f7e31d Change browse icon (#188)
Now matches new icon

Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>

Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-17 14:13:38 -07:00
Joseph Mattello
c0b125b042 Info.plist add LSSupportsOpeningDocumentsInPlace 1
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 04:29:10 -05:00
Joseph Mattello
9d5166474b AltWidget version set to $(MARKETING_VERSION)
fixes xcode warning that version of app and extension don’t match

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 04:28:53 -05:00
Joseph Mattello
499debd9b1 fix 3 more style .white deprecations to .medium
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:13:30 -05:00
Joseph Mattello
9421c2dd6e fix deprecated style .white to .medium
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:12:13 -05:00
Joseph Mattello
0a240a836a fix xcode warning
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:11:22 -05:00
Joseph Mattello
864f0fcd77 Add AltStore Release scheme
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:10:45 -05:00
Joseph Mattello
36909e87c4 fix crash for missing patreon images
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:10:28 -05:00
Joe Mattiello
9e37ace1e3 Merge pull request #181 from LitRitt/change-designer
Update app icon and designer
2022-12-11 21:56:58 -05:00
LitRitt
c34019ad58 Update Settings.storyboard
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-11 15:54:18 -08:00
LitRitt
0058a81af5 Update SettingsViewController.swift
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-11 15:54:18 -08:00
LitRitt
2c3aa781a0 Update App Icon
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-11 15:54:18 -08:00
jawshoeadan
279c305431 Merge pull request #179 from jawshoeadan/beta-apps
Fix beta apps on the my apps view, and fix deactivating/activating apps
2022-12-11 16:43:46 -06:00
Jawshoeadan
84f5131b8a Remove all restrictions based on Patreon account (hopefully) 2022-12-11 15:41:43 -06:00
Jawshoeadan
e0cbf787a5 Fix beta apps on the my apps view, and fix deactivating/activating apps 2022-12-08 19:24:28 -06:00
Jawshoeadan
7d6e25b40b Remove Patreon check to show beta apps in store view 2022-12-08 10:28:34 -06:00
Spidy123222
480c18f583 add pokemmo into trusted sources. (#176) 2022-12-07 16:36:17 -08:00
Joshua Laymon
c44cad216c Merge pull request #171 from bogotesr/settings
Better anisette settings
2022-12-05 11:39:08 -07:00
bogotesr
17b264904d Add crystal's server 2022-12-04 15:43:08 -07:00
bogotesr
5e5f1eeebd describe what the toggle does 2022-12-03 21:19:31 -07:00
bogotesr
bf6908fdd4 Better anisette settings
adds multivalue selector for some anisette servers
2022-12-03 13:48:33 -07:00
Joe Mattiello
f5c684ffaf Merge pull request #164 from SideStore/update-em-submodules 2022-12-01 20:02:04 -05:00
JJTech0130
b4cea549e0 update em_proxy and minimuxer submodules 2022-11-30 18:17:01 -05:00
Fabian Thies
3f7fda021f Add missing App Permission Types (#159)
* Add missing app permission types

* Remove old unused icons for photos, background fetch and background audio permission types

* Add missing icons for contacts and reminders permission types

* Add missing camera permission icon and name

* Switch permission icons to filled versions for a more cohesive look
2022-11-29 06:30:51 -07:00
Jackson Coxson
e90ae4b4fc Add an issue template (#157)
Update issue templates
2022-11-26 19:34:52 -07:00
Spidy123222
33cf25b8f8 Update app.json
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 21:54:37 -08:00
Joe Mattiello
b48c7e61fc Merge pull request #143 from SideStore/feature/fix_xcconfig_widget 2022-11-23 00:01:33 -05:00
Joseph Mattello
af8436e645 Fix xcconfig vars for widget
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-22 23:06:45 -05:00
Joe Mattiello
b55b69cd47 Merge pull request #141 from SideStore/Version
Bump version to 0.1.1
2022-11-22 22:22:49 -05:00
Spidy123222
bb56af3068 Change wording.
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 22:20:37 -05:00
Spidy123222
59d7e9fa0c For old client give message update
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 22:20:37 -05:00
Spidy123222
e43e7144ae Update project.pbxproj
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 22:20:37 -05:00
Spidy123222
5a71b29ae4 Update Build.xcconfig
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 22:20:37 -05:00
Joe Mattiello
40b47329a3 Merge pull request #122 from SideStore/pullrequests/bogotesr/develop
Rebase of #62
2022-11-22 22:18:10 -05:00
Joseph Mattello
1be6432ecc Add TODO notes for PR #122 notes
what to do with patreon table view and url

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-22 22:16:56 -05:00
Joshua Laymon
952be95c36 Add files via upload
Signed-off-by: Joshua Laymon <71040782+bogotesr@users.noreply.github.com>
2022-11-22 22:16:56 -05:00
Spidy123222
dcc9fd83e7 Update AltStore.xcconfig
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 22:16:56 -05:00
Spidy123222
f1df742a40 Try fixing compile error
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 22:16:56 -05:00
Spidy123222
80058f2493 Remove mention of sideserver in app 2022-11-22 22:16:56 -05:00
Joe Mattiello
c77c69c491 Merge pull request #45 from SideStore/feature/noserver
Remove SideServer from monorepo
2022-11-22 22:06:30 -05:00
Joseph Mattello
08633c8bd0 Remove Server from xcode proj
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-22 21:35:34 -05:00
Joseph Mattello
12e90fa5e6 Remove Server files/folders
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-22 10:13:23 -05:00
Joe Mattiello
8ceefe4420 Merge pull request #127 from SideStore/pullrequests/jkcoxson/develop
Anisette URL - Pullrequests/jkcoxson/develop
2022-11-22 10:11:51 -05:00
Joseph Mattello
280502ec03 AnisetteManager UserDefaults.standard from .shared
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 21:24:40 -05:00
Joseph Mattello
5b6f09af4d info.plst add mobiledevicepairing imported type
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
763f55a316 Remove customAnisetteURL empty string default
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
8c4608f626 refactor anisette manager to own file
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
e21cdc114b Settings.bundle group and text type URL
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
4eb472303e ORGANIZATIONNAME to SideStore
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
5e864562e1 FetchAnisetteDataOperation.swift fix url reading
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
343b75366c OSLog+SideStore fix staticstring
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
e86491cfa2 AltStore scheme XCode auto edits
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
4aebfd7e3e Anisette URL fix missing abort
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
c8770fd2cd OSLog+SideStore update copyright
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
a0aa141f95 Anisette URL convenience methods and logging
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Jackson Coxson
4514fd3745 Remove customAnisetteURL from default Info.plist 2022-11-21 20:54:55 -05:00
Jackson Coxson
aa43f27124 Choose a pairing file at runtime 2022-11-21 20:54:55 -05:00
Jackson Coxson
a62605964b Add settings bundle 2022-11-21 20:54:55 -05:00
Jackson Coxson
b839c07bae Add default fields for custom anisette 2022-11-21 20:54:55 -05:00
Jackson Coxson
d519ae15a8 Set anisette URL from plist value 2022-11-21 20:54:55 -05:00
Spidy123222
a9ee08b6b3 Reorder trusted sources
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-21 20:24:57 -05:00
Joe Mattiello
c138d11eb7 Merge pull request #128 from Spidy123222/Fix/signing-bundleid
Fix Refreshing sidestore and staging error.
2022-11-18 21:25:42 -05:00
Spidy123222
7e56057681 Fix refreshing to use normal bundleid
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-17 18:16:21 -08:00
Spidy123222
73128db799 Update Bundle+AltStore.swift
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-17 17:08:58 -08:00
Spidy123222
e6b52ea0e8 Update AltStore.xcconfig
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-17 17:08:11 -08:00
Spidy123222
db436c18f3 Update Build.xcconfig
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-17 17:07:48 -08:00
jawshoeadan
4ad2e11c21 Update README to include Rust specific build instructions (#124)
Update README.md

Signed-off-by: jawshoeadan <62785552+jawshoeadan@users.noreply.github.com>

Signed-off-by: jawshoeadan <62785552+jawshoeadan@users.noreply.github.com>
2022-11-16 14:14:41 -07:00
Spidy123222
54e9fe5f9d Change version on to reflect being pre-release (#123)
* Change source url and version in app.json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Change app.json to reflect SideStore/apps.json

* Change the source to be future default

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
Co-authored-by: Nythepegasus <nythepegasus84@gmail.com>
2022-11-15 22:23:30 -07:00
Jackson Coxson
62233e59f8 Show error message when pairing file is missing (#118)
* Show alert on startup error

* Call super to load normal view
2022-11-13 18:40:33 -07:00
Joe Mattiello
3dd561ca3a [#102][#101] Loading view error handling, Load pairingfile from docs, bundle, plist (#112)
* refs #102 load pairingfile from docs, bundle,plist

tries Documents/ALTPairingFile.plist, then app bundle resources ALTPairingFile.plist, finally Info.plist with non default value

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* refs #102 change plist to mobiledevicepairing

fixes xcode compiling .plist resources

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-08 12:15:09 -07:00
Joe Mattiello
2fc3559fd2 Add .editorconfig file (#109)
Signed-off-by: Joseph Mattello <mail@joemattiello.com>

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-08 12:10:39 -07:00
Joe Mattiello
b7bf9c7ec0 Add github CODEOWNERS file (#108)
* Add .github/CODEOWNERS

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* Edit CODEOWNERS add jkcoxson

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-08 12:09:47 -07:00
Joe Mattiello
8e50874e0c Create Makefile (#99)
* add Makefile

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* Makefile fix leftover test code

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-08 12:09:33 -07:00
Spidy123222
3b217915dc Change version to 1.0.0 (#96)
* Change app list to 1.0.0

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Change build version.

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* change more to 1.0.0

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-06 01:38:43 -06:00
Joshua Laymon
39f468a88f Match branding some more (#72)
* change app name to SideStore
* Make it P U R P L E
* Fix actions for branding
* Change patreon description in settings
* Update RefreshAttemptsViewController.swift
* Update LaunchViewController.swift
* Update some of Credits
* Fix space
* More Branding fixes
* Change How it Works to have better wording.
* Change Branding for source and bundle
* Get the Rest of the Branding and popup naming fixes
* Welcome to SiteStore
* Update instructions to not include sideserver 

This removes sideserver fro the instructions on how it works as sideServer in background of a computer so far isn’t needed.
* Remove mention of sideserver in the app
* SideStore error Message instead of AltServer
* some adjustments
* forgot to add this in the last commit
* Try fixing compile error
* Spell correct of last commit
* Fix Requested changes 11/5/2022

Signed-off-by: bogotesr <71040782+bogotesr@users.noreply.github.com>
Co-authored-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-05 23:50:07 -07:00
Spidy123222
5f1a8a0f60 Fix secret tunnel header to em proxy
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-05 16:17:23 -07:00
JJTech
4ade0cfd56 Update README.md (#90)
* Update README.md

Signed-off-by: JJTech <jjtech@jjtech.dev>

* Add minimuxer to list of libraries in read

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Change wording for minimuxer

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Change Secret tunnel to be em-proxy and add detail

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: JJTech <jjtech@jjtech.dev>
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
Co-authored-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-05 16:53:09 -06:00
Spidy123222
797be3c7ad Add pojavlauncher to trusted sources (#107)
* Add Pojavlauncher source

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update trustedapps.json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update trustedapps.json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update FetchTrustedSourcesOperation.swift

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update trustedapps.json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update FetchTrustedSourcesOperation.swift

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-05 16:50:35 -06:00
Jackson Coxson
3fbfc849ff Implement emotional damage (#95)
* Implement em_proxy

* Update libimobiledevice

* Add minimuxer library to Xcode

* Build missing C files for libimobiledevice

* Remove objective C library

* Add pairing file to Info.plist

* Heartbeat self on startup

* Enable JIT on-device

* Implement on-device installation

* Fix OpenSSL header errors

* Random submodule bullcrap go

* Search release folder for emotional damage

* Clean dependencies

* Build Rust dependencies attempt 1/999

* Update em_proxy

* Implement refreshing apps

* Clean up old operations

* Remove all AltServer code

* Remove files from Xcode project

* Implement auto mounting the developer DMG

* Recover from app being backgrounded

* Fixed keeping pairing file in app after updating SideStore (#3)

* Use compliant error handling for minimuxer

* Fix app failing to install

* Don't kill proxy on backgrounding

* Makes sure the ALTPairingFile gets transferred even if team IDs change (#4)

* Step 1 to allow SideStore to resign itself

* Update ResignAppOperation.swift

* Adding cache for action runner (#5)

* Start caching commit for actions

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update build.yml

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update build.yml

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Use rust lib directories to cache

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Cache cargo also

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Fix spacing

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Replace cargo id for caching

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Remove cache if statements

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Add disconnected WireGuard detection

* Add minimuxer logging

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
Co-authored-by: jawshoeadan <62785552+jawshoeadan@users.noreply.github.com>
Co-authored-by: Joelle Stickney <joellestickney@gmail.com>
Co-authored-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-02 18:58:59 -06:00
Joshua Laymon
4094338370 Use a custom default source for SideStore (#71)
* Adds a Custom default source and allows Updating app via ota.

Co-authored-by: Spidy123222
2022-10-18 01:10:18 -07:00
Spidy123222
0cb5de61b5 README: Make app names in compile use Side names (#89)
Make app names in compile steps use Side names
2022-09-27 11:48:13 -07:00
jawshoeadan
7fbeb7adbb Get anisette from HTTP GET request instead of AltServer (#87) 2022-09-16 11:52:53 -06:00
Spidy123222
1f8fe35a22 Make Sidestore use its own trustedapps json (#58)
* Add trusted apps list json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Force to use apps.json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Rename apps.json to trustedapps.json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Redo trustedlink

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update FetchTrustedSourcesOperation.swift

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Add provenance-emu source

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-09-14 13:24:04 -07:00
Spidy123222
471451bf35 Revert "Revert "Change settings altstore names to SideStore"" (#82) 2022-09-14 04:45:33 -07:00
Spidy123222
ea0b162288 Merge pull request #81 from SideStore/revert-61-Change-settings-AltStore-names-to-side
Revert "Change settings altstore names to SideStore"
2022-09-14 04:39:01 -07:00
Spidy123222
e5052acebf Revert "Change settings altstore names to SideStore" 2022-09-14 04:38:34 -07:00
Spidy123222
32d3ae1fa3 Merge pull request #61 from Spidy123222/Change-settings-AltStore-names-to-side 2022-09-14 04:37:07 -07:00
Spidy123222
9523afa0c5 Revert Patreon Description to avoid conflict.
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-09-14 04:27:26 -07:00
Spidy123222
faa1ddf36d Remove patreon footer description
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-09-14 04:19:56 -07:00
Spidy123222
d256306adf Merge branch 'develop' into Change-settings-AltStore-names-to-side 2022-09-13 22:58:07 -07:00
Spidy123222
60d5b932f2 Change Xcode version to 14.0.0 (#79)
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-09-13 21:23:46 -06:00
JJTech
b89153cafc Merge branch 'develop' into Change-settings-AltStore-names-to-side 2022-09-01 19:15:31 -04:00
JJTech
844d60ae25 Bump minimum Xcode version to Xcode 14 (#64)
* Remove Xcode 13 from the build matrix
* Bump minimum Xcode version to Xcode 14
Signed-off-by: JJTech <jjtech@jjtech.dev>
2022-09-01 17:59:50 -04:00
Spidy123222
74f0300ca2 Merge branch 'SideStore:develop' into Change-settings-AltStore-names-to-side 2022-08-31 13:24:57 -07:00
Spidy123222
de3e895aac Merge pull request #52 from jawshoeadan/develop
Adds fakesign and sets default team id to be com.SideStore to fix AltServer sideloading
2022-08-31 13:14:36 -07:00
Spidy123222
79f3bcb8ac Merge branch 'develop' into develop
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-08-31 11:25:28 -07:00
Spidy123222
7d08abc52a Change wording of accessing local traffic
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-08-31 11:15:52 -07:00
Spidy123222
a3ab498b2f Update SettingsViewController.swift
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-08-31 05:11:15 -07:00
JJTech
ea8e99f7d2 Convert to build matrix (#56)
* Convert to build matrix
* turn off fail-fast strategy
we don't want all the jobs cancelled just because it didn't build in an old version of Xcode
* only trigger push build when pushing to master or develop

Signed-off-by: JJTech <jjtech@jjtech.dev>
2022-08-23 18:13:20 -04:00
JJTech
fadb95ca68 Use Xcode 14 (#55)
New iOS 16 features require Xcode 14
Xcode 14 is only available on macOS 12 runners

Signed-off-by: JJTech <jjtech@jjtech.dev>
2022-08-19 11:35:31 -04:00
Nythepegasus
d66f30e6a2 Merge remote-tracking branch 'refs/remotes/upstream/develop'
Conflicts:
	AltStore.xcodeproj/project.pbxproj
	AltStore/AppDelegate.swift
2022-08-19 01:14:16 -04:00
Riley Testut
c27b93e8b5 Adds iOS 16 Lock Screen widget 2022-08-17 15:33:13 -05:00
Riley Testut
3ec9fc7370 [Apps] Updates Delta beta to 1.4b2 2022-08-17 15:27:19 -05:00
Riley Testut
7ffa7df8bf Fixes “stored properties cannot be marked @available” compiler error
Xcode 13 and earlier allowed us to mark lazy stored properties with @available, but it turns out this was never actually supported. Xcode 14 now throws a compiler error, so we work around it by converting lazy @available properties into computed properties, backed by typed-erased lazy ivars.
2022-08-17 15:23:17 -05:00
Riley Testut
c7e69f4954 [Apps] Adds “New to AltStore?” news to highlight revamped FAQ
Also removes old “Welcome to AltStore” news
2022-07-28 11:41:52 -05:00
Riley Testut
ec6d6db428 [Apps] Updates AltStore to 1.5.1 2022-07-28 11:22:55 -05:00
Riley Testut
26ba2aa221 [AltPlugin] Fixes crash when device’s serial number is nil
For unknown reasons, AKDevice.serialNumber can sometimes return nil. As a workaround, we just fall back to a hardcoded valid serial number if AKDevice.serialNumber is nil.
2022-07-26 13:38:05 -05:00
Riley Testut
d661a75334 Updates app version to 1.5.1 2022-07-13 11:43:08 -05:00
Josh-WikiRealty
155e47055e Generalized everything to match SideStore branding for workflow
Sideloading now works with AltServer
2022-07-04 17:30:50 -07:00
Josh-WikiRealty
6859b30191 Fakesign app in workflow 2022-07-03 01:03:48 -07:00
Josh-WikiRealty
bf4763fdd2 Add entitlements file and update gitignore 2022-07-03 00:53:02 -07:00
jawshoeadan
ec9e793f27 Merge branch 'SideStore:develop' into develop 2022-07-02 13:38:30 -07:00
JJTech
55f172fcc8 Add build badge, fix PR badge style (#50)
* Add build badge, fix PR badge style

Signed-off-by: JJTech <jjtech@jjtech.dev>

* Badge links HTTPS

Signed-off-by: JJTech <jjtech@jjtech.dev>
2022-06-23 11:47:17 -04:00
JJTech
08c9c746bf Put back Package.resolved, have CI remove it (#49)
* Remove swift resolved packages from git ignore

* Add Package.resolved to

* Have CI remove Package.resolved
2022-06-23 11:47:01 -04:00
JJTech
800ae65ee4 Fix CI (#47)
* Try downgrading to macOS 11

Signed-off-by: JJTech <jjtech@jjtech.dev>

* Delete Package.resolved

Signed-off-by: JJTech <jjtech@jjtech.dev>

* Add Packagage.resolved to .gitignore

Signed-off-by: JJTech <jjtech@jjtech.dev>
2022-06-22 10:29:05 -04:00
JJTech
10fef82714 Add InteliJ to git ignore (#44)
Ignore AppCode/InteliJ .idea folder
2022-06-21 13:39:46 -06:00
jawshoeadan
aa9f6da276 Merge branch 'SideStore:develop' into develop 2022-06-20 18:18:05 -07:00
jawshoeadan
eb5a80eeba Add CI/CD workflow (#43) 2022-06-20 15:23:05 -06:00
jawshoeadan
59d4301624 Merge branch 'SideStore:develop' into develop 2022-06-20 14:21:33 -07:00
Josh-WikiRealty
7ef1484f73 Add CI/CD workflow 2022-06-20 14:18:34 -07:00
JJTech
874791a480 Convert AltSign and OpenSSL into proper Swift packages (#42)
Convert AltSign and OpenSSL to Swift packages
2022-06-20 14:27:28 -06:00
JJTech
42a5a70015 Merge pull request #26 from JJTech0130/patch-4
Make it more consistent
2022-06-20 16:15:53 -04:00
Joe Mattiello
d8e2fbe6c8 Rename AltStore with variables (#17)
* iOS 14 for xcode 14 errors

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* add group.APP_GROUP to more plists

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* reorder altid groups

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* update to newer sparkle api

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* fix warnings in altsign and libmobdevice

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

Co-authored-by: JJTech <jjtech@jjtech.dev>
2022-06-20 13:13:15 -06:00
Nythepegasus
1c5794c203 Merge pull request #27 from rileytestut/develop
Pull from Upstream
2022-06-13 18:47:32 -04:00
JJTech
4d2df6b4b4 Make it more consistent
Signed-off-by: JJTech <jjtech@jjtech.dev>
2022-06-13 18:39:34 -04:00
Riley Testut
f3b2e1f886 [AltPlugin] Updates version to 1.10 2022-06-09 17:44:36 -07:00
Riley Testut
a3da478301 [AltPlugin] Supports macOS 13 Ventura beta 1 2022-06-09 17:44:06 -07:00
JJTech
4a37e15ca5 Replace xcodeworkspace with xcodeproj (#20) 2022-06-08 17:38:36 -06:00
JJTech
ab03790c2f Update build instructions (#19)
* Replace redundant step with recursive clone

See https://stackoverflow.com/a/4438292

* Fix code signing instructions, formatting

* Clarify step 3 (embedding the UDID)
2022-06-08 17:32:02 -06:00
JJTech
6cca017cd2 Replace redundant text with link (#18) 2022-06-08 17:13:25 -06:00
Joe Mattiello
06f91c8c9a Merge pull request #16 from SideStore/feature/XCConfig2
XCConfig for bundle/sign ids
2022-06-07 08:14:48 -04:00
Joseph Mattello
c26f6b002c xcode touching packages
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:10:23 -04:00
Joseph Mattello
393342402a refactor sparkle URLs to info.plist
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:10:13 -04:00
Joseph Mattello
820d2e53d0 spm touches
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:01:27 -04:00
Joseph Mattello
af8c26dcf9 AltSign remove warning
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:01:27 -04:00
Joseph Mattello
a9fa1c4d69 Remove workspace requirement for xcodeproj
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:01:27 -04:00
Joseph Mattello
ef732712ee AltStore & AltServer builds with XCConfig
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:01:27 -04:00
Joseph Mattello
85985cce3b Use XCConfig files for codesign and bundle ids
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:01:26 -04:00
Joe Mattiello
4f93d4770f Merge pull request #15 from SideStore/feature/carthage2
Remove Cocoapods for Swift Packages
2022-06-07 06:00:07 -04:00
Joseph Mattello
2f3761645c delete unused podfiles
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 05:22:17 -04:00
Joseph Mattello
b2ad5eaf85 temp comment out Sparkle.app codesign
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 04:02:22 -04:00
Joseph Mattello
03dc905c41 oops
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 04:02:22 -04:00
Joseph Mattello
1e82eb4d71 altserver depends to swiftpm
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 04:02:22 -04:00
Joseph Mattello
89c2947c2a AltStore.app works with SwiftPM
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 03:31:33 -04:00
Joseph Mattello
3008eeb33f altstore app target ios only
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 03:15:54 -04:00
Joseph Mattello
d4515a5fff altstorecore builds using forked altsign
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 03:14:54 -04:00
Joseph Mattello
4351d4d4e0 keychain access as swift module
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 03:14:34 -04:00
Joseph Mattello
b9c27c3c3e remove superfluous code signing on framework
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 02:36:43 -04:00
Joseph Mattello
1051882c72 altsorecore macOS only
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 02:33:52 -04:00
Joseph Mattello
3a177e5bb8 libimobiledevice submodule fork change
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 02:30:03 -04:00
Joseph Mattello
47f805dc79 de-intergrate cocoapods
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 02:11:28 -04:00
Joseph Mattello
49670d4723 altserver target macos only
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 02:11:28 -04:00
Joe Mattiello
6c3c7c2377 Merge pull request #14 from SideStore/feature/MailRelaunch
Check if mail is open b4 launch
2022-06-07 02:02:19 -04:00
Joseph Mattello
ef4aa215ed mail.app only launch if not open
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-06 23:41:58 -04:00
Joe Mattiello
3b8de7f742 Merge pull request #4 from SideStore/feature/multiPlatform
Multi-platform app target support (tvOS, watchOS etc)
2022-06-03 23:36:34 -04:00
Joseph Mattello
bb75eb98e8 AltStoreCore multi-platform URLs
Signed-off-by: Joseph Mattello <mail@joemattiello.com>

apps.json add platform url support

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-03 23:36:13 -04:00
Joe Mattiello
d0b0ef4d43 Merge pull request #13 from SideStore/pullrequests/jawshoeadan/master
Automatically open mail when fetching Anisette data and update error …
2022-06-03 23:35:40 -04:00
Jackson Coxson
8c3cb9bce4 Merge branch 'rileytestut:develop' into develop 2022-06-03 17:21:51 -06:00
Riley Testut
6d27f23626 [Apps] Updates Delta beta to 1.4b1 2022-06-02 14:01:04 -07:00
Josh-WikiRealty
c7f99a87fb Automatically open mail when fetching Anisette data and update error message to accomodate 2022-06-02 02:03:11 -04:00
Joe Mattiello
7953ccacd9 Merge pull request #12 from jkcoxson/develop
Add multi team view
2022-06-02 02:00:35 -04:00
Joseph Mattello
6fb476213b update gitignore
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-02 00:29:21 -04:00
Jackson Coxson
b4fda914bf Add team view controller to Xcode's list of files 2022-05-31 00:02:57 -06:00
Jackson Coxson
1217e81fdd Prefer local servers over VPN 2022-05-30 23:53:22 -06:00
Jackson Coxson
0ed1722e7b Merge branch 'SideStore:develop' into develop 2022-05-30 23:44:26 -06:00
Jackson Coxson
c11af230e5 Add view for multiple teams from Megarush 2022-05-30 23:43:47 -06:00
Spidy123222
0d0f9f9159 Change Readme (#11)
* Update README.md

* change casing of SideStore

* Add roxas and replace git

* Additional detail for Netmuxd

* fix wording

this fixes the wording of Licensing and building instructions to be simplified.

* Correct spelling AltStore

* Forgotten S
2022-05-30 23:14:00 -06:00
Jackson Coxson
1560b3cfe6 Revert "Change AltStore naming to SideStore"
This reverts commit 20182b78f5.
2022-05-30 22:54:21 -06:00
Jackson Coxson
20182b78f5 Change AltStore naming to SideStore 2022-05-30 22:46:21 -06:00
Jackson Coxson
fd8c39e689 Change icon and colors 2022-05-30 22:05:26 -06:00
Jackson Coxson
31da0ec3fb Merge branch 'rileytestut:develop' into develop 2022-05-30 11:26:13 -06:00
Jackson Coxson
bcc390fdc1 Add manual connection (#10)
* Add manual connection

* Send request to netmuxd for a manual connection
2022-05-30 11:25:54 -06:00
Joe Mattiello
4eda7577fc AltStore add iPad to device family (#5)
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-05-30 10:51:10 -06:00
Joe Mattiello
9a9df2841b WiFi to Wi-Fi spelling (#6)
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-05-30 10:50:03 -06:00
Riley Testut
0dc5557e68 [Apps] Updates AltStore beta to 1.5.1b 2022-05-27 12:36:43 -07:00
Riley Testut
01c8d42790 Updates app version to 1.5.1b 2022-05-26 18:27:14 -07:00
Riley Testut
8463e8f1ee Updates ALTServerID to Purple M1 iMac 2022-05-26 18:26:35 -07:00
Riley Testut
7909545356 [AltServer] Updates app version to 1.5.1b 2022-05-26 18:11:57 -07:00
Riley Testut
070839b9e2 Fixes authenticating with old email address after changing Apple ID’s primary email 2022-05-25 16:45:27 -07:00
Riley Testut
b0dd15e723 Fixes “Application is missing the application-identifier entitlement” error 2022-05-25 16:23:45 -07:00
Riley Testut
93d9645adc [AltServer] Fixes incorrect “Developer Disk incompatible with [OS version]” error
Previously we assumed that if there was an error installing the developer disk, it was incompatible with the device’s iOS version. Howevever, sometimes an iOS device needs to be rebooted before it can successfully mount a developer disk.

We now explicitly check for the latter scenario, and present a different error message asking the user to reboot their device if that’s the case.
2022-05-25 15:57:17 -07:00
Riley Testut
2244de0faf Fixes crash when presenting unrecognized ALTServerError’s 2022-05-25 15:31:04 -07:00
Riley Testut
7bf813e7ec [AltServer] Fixes installing AltPlugin after uninstalling it
Bundle(url:) is cached, so even if AltPlugin is deleted Bundle(url:) will still return a non-nil value. Instead, we now directly check whether a directory exists at pluginURL to determine if AltPlugin is installed.
2022-05-25 15:17:58 -07:00
Riley Testut
d3a8a89023 [AltServer] Fixes disconnecting ALTWiredConnection’s
ALTWiredConnection.disconnect() doesn’t do anything if ALTWiredConnection.isConnected == NO. The problem is, we never set .isConnected to YES in the first place…which means disconnect() never actually did anything. Whoops.
2022-05-25 15:08:03 -07:00
Riley Testut
7b973ac447 [AltServer] Fixes NetworkConnection strong reference cycle
WirelessConnection.nwConnection.stateUpdateHandler maintains strong reference to WirelessConnection, resulting in strong reference cycle. To break it, we now explicitly set stateUpdateHandler to nil when disconnecting.
2022-05-25 14:59:12 -07:00
Riley Testut
c91c5dce8c Supports 2FA Apple IDs with no trusted devices
Falls back to sending 2FA code via SMS if there are no registered trusted devices.
2022-05-16 16:12:52 -07:00
Riley Testut
ae2fafaa9e Fixes authenticating Apple IDs with capital letters
Also fixes repeatedly asking some users to sign in with Apple ID.
2022-05-12 15:53:05 -07:00
Riley Testut
611e36ae25 [Apps] Updates AltStore to 1.5 2022-05-10 15:05:34 -07:00
Riley Testut
48a3a7f61b [AltServer] Fixes indefinitely caching STAGING Sparkle URL 2022-05-10 15:02:58 -07:00
Riley Testut
92e6543b78 [Apps] Adds AltServer 1.5 news item 2022-05-05 14:03:34 -07:00
Riley Testut
8c15de887a Updates app version to 1.5 2022-05-04 12:47:53 -07:00
Riley Testut
ea0de71b3a Enables AltJIT for public versions 2022-05-04 12:46:14 -07:00
Riley Testut
36817b801e [AltServer] Updates NSMultipleUnderlyingErrorsKey #available check to include macOS 2022-04-20 15:19:29 -07:00
Riley Testut
1a682f1c06 [AltServer] Updates app version to 1.5 2022-04-20 15:14:21 -07:00
Riley Testut
98d1fc260a [Apps] Updates AltStore beta to 1.5rc 2022-04-19 13:20:40 -07:00
Riley Testut
3b83018106 Updates app version to 1.5rc 2022-04-18 16:35:14 -07:00
Riley Testut
2f329cd2aa Migrates Core Data model from v9 to v10 2022-04-18 16:01:48 -07:00
Riley Testut
a28a92b99e Updates Keychain.patreonAccountID in PatreonAPI.fetchAccount()
PatreonAPI.fetchAccount() is called by both PatreonAPI.authenticate() and PatreonAPI.refreshPatreonAccount(), so this ensures the keychain is updated via both ways.
2022-04-18 15:46:57 -07:00
Riley Testut
2ff5afa22a Replaces PatreonAccount.isFriendZone with ManagedPatron
Rather than store both the current user’s Patreon account and all cached Friend Zone patrons in the same table, we now store Friend Zone patrons in the new ManagedPatron table. This avoids the need to distinguish between the two at runtime.
2022-04-18 15:46:35 -07:00
Riley Testut
f97187dba5 Adds (Managed)Patron Core Data entity
Will be used to cache Friend Zone patrons separately than the existing PatreonAccount entity.
2022-04-18 15:25:27 -07:00
Riley Testut
60005a3d7f Throws URLError.fileDoesNotExist for 404 responses 2022-04-14 18:29:34 -07:00
Riley Testut
b6f88c329c Updates ALTDeviceID to iPhone 13 Pro 2022-04-14 18:15:23 -07:00
Riley Testut
e41d46082d Unhides Sources button for public builds 2022-04-14 18:01:20 -07:00
Riley Testut
be31f40288 Updates Keychain.patreonCreatorAccessToken via UpdatePatronsOperation 2022-04-14 17:58:06 -07:00
Riley Testut
3fda2bb400 Caches Friend Zone patrons to offset slow loading time
The Patreon API doesn’t have a way to fetch just the patrons belonging to our Friend Zone tier. Instead, we need to fetch ALL patrons (including inactive ones) and filter out those not in the tier. This is very inefficient, and takes over a minute to complete as of April 14, 2022, due to the number of patrons we have.

We can’t do much to change this, but AltStore will now at least cache the fetched patrons with Core Data. Additionally, AltStore will only perform this long fetch whenever the Friend Zone list actually changes, rather than every time the Patreon screen appears.
2022-04-14 17:56:36 -07:00
Riley Testut
c935adf8bd Uses Keychain.patreonAccountID to fetch current user’s PatreonAccount
Allows us to distinguish between the current user and other cached patrons in the future.
2022-04-14 16:37:29 -07:00
Riley Testut
33b261b0eb Supports adding trusted sources from SourcesViewController
Previously, only the beta version of AltStore could add sources. Now, the public version supports adding explicitly “trusted” sources, while the beta version can continue to add any source.
2022-04-14 16:33:49 -07:00
Riley Testut
faf5123498 Adds FetchTrustedSourcesOperation 2022-04-14 15:27:57 -07:00
Riley Testut
64cf6fb443 #ifdef’s out libfragmentzip and AppleArchive usage for simulator builds
Neither are supported by the iOS simulator.
2022-04-13 20:41:38 -07:00
Riley Testut
9d39adb358 Adds Shane Gill to Settings credits 2022-04-13 20:06:57 -07:00
Riley Testut
a7b48a988f Adds Shane to Patreon screen 2022-04-13 20:06:35 -07:00
Riley Testut
87874319d3 Fixes Core Data error when not connected to internet
NSError.sanitizedForCoreData() now sanitizes _all_ user info values (including for underlying errors) to ensure they all conform to NSSecureCoding, rather than just removing “NSCodingPath” value (if it exists).
2022-04-11 13:42:31 -07:00
Riley Testut
7ccd69b978 Hides permission section if app doesn’t list any permissions
Eventually, listing permissions will be mandatory so AltStore can verify that apps only require the permissions they declare. Until then, we’ll allow apps to not list their permissions.
2022-04-11 12:31:02 -07:00
Riley Testut
0dfcd0ba47 Supports landscape app screenshots
Rotates landscape screenshots before displaying them. For now, we still assume screenshots have 16:9 aspect ratio.
2022-04-11 11:59:56 -07:00
Riley Testut
859f481e20 Fixes News tab crash after adding/removing sources with NewsItems 2022-04-07 14:43:03 -07:00
Riley Testut
0a9e10d55b [AltServer] Updates app version to 1.5rc 2022-03-31 12:50:52 -07:00
Riley Testut
789e6200f4 Disables app group feature if app has no app groups
We can’t remove all app groups from an App ID once one has been assigned, but we _can_ disable app groups completely for effectively the same result.

This fixes some apps having permanant access to AltStore’s own app group after being (de-)activated.
2022-03-31 12:50:50 -07:00
Riley Testut
be06c9bacd [AltServer] Fixes “App Group does not exist” error 2022-03-29 19:51:54 -07:00
Riley Testut
9fdd9b11e3 [AltServer] Replaces lock with semaphore when updating app groups
Locks can’t be unlocked from a separate thread than they were locked on…whoops.
2022-03-29 19:51:17 -07:00
Riley Testut
80a3a6e246 [AltServer] Embeds ALTServerID in Info.plist if app uses AltKit 2022-03-29 19:34:47 -07:00
Riley Testut
44cfab7234 [AltServer] Adds ellipsis to menu items that require additional input
As specified by the macOS HIG.
2022-03-29 16:14:00 -07:00
Riley Testut
3cf21af44f Fixes crashing due to uncaught codesigning exceptions 2022-03-29 16:09:43 -07:00
Riley Testut
6d370ca7ca [AltServer] Fixes sideloading apps to devices running iOS 9.3 or later 2022-03-29 16:07:38 -07:00
Riley Testut
57a65ed551 [Apps] Updates AltStore 1.4.9 release notes 2022-03-07 10:52:48 -08:00
shanegillio
1022bc4b6e [Apps] Updates AltStore to 1.4.9 (#960)
* [Apps] Updates AltStore beta to 1.5b4
* [Apps] Updates AltStore to 1.4.9
* Apply suggestions from code review
2022-03-04 12:54:02 -08:00
Riley Testut
f48d16c65c [AltServer] Fixes prematurely fetching installed apps 2022-03-02 16:22:04 -08:00
shanegillio
cae346d2b8 [Apps] Updates AltStore beta to 1.5b4 (#957) 2022-03-02 11:34:27 -08:00
Riley Testut
1fb96a5ef2 [AltServer] Improves ALTServerErrorIncompatibleDeveloperDisk error message
Uses NSError’s debug description, if available, to populate error alerts.
2022-03-01 16:07:20 -08:00
Riley Testut
380f10286d [AltServer] Ignores incompatible cached developer disks
Fixes issue where AltServer would always use cached developer disk, even if it isn’t compatible with the device’s operating system version.
2022-03-01 16:03:03 -08:00
Riley Testut
ef4c038b0b [Apps] Adds #StandWithUkraine news update 2022-03-01 12:04:57 -08:00
Riley Testut
1c5b59737d Replaces direct Patreon URL with forwarding URL
Allows us to change the Patreon URL without also updating the app.
2022-02-25 15:43:25 -08:00
Riley Testut
fdf9d7f320 Updates app version to 1.5b4 2022-02-23 13:42:40 -08:00
Riley Testut
f10cec5062 Fixes crashing on launch on iOS 13 or earlier
We now weak link libAppleArchive, which doesn’t exist prior to iOS 14.
2022-02-22 16:07:03 -08:00
Riley Testut
2bdf89e83f [AltServer] Fixes staging AltPlugin update URL 2022-02-22 12:35:24 -08:00
Riley Testut
1ebdb8df21 [AltServer] Updates AltPlugin separately from AltServer
Code written and committed with Lil’ Dude “Weedles” by my side <3
2022-02-15 14:44:11 -06:00
Riley Testut
31e9ce0d40 [AltServer] Uses ephemeral URLSession when fetching developer disks
Fixes AltServer using outdated cached response after updating developer disks for a new OS version.
2022-02-09 13:52:11 -08:00
Riley Testut
cd300190be [AltServer] Uses Sparkle staging URL for STAGING builds 2022-02-09 13:46:03 -08:00
Shane Gill
6588e558fa [AltServer] Updates app version to 1.5b10 2022-02-09 08:59:33 -08:00
Shane Gill
ca34c36df6 [AltServer] Updates AltPlugin to 1.9 2022-02-08 18:59:35 -08:00
Shane Gill
db4169488e [AltPlugin] Updates version to 1.9 2022-02-08 18:26:01 -08:00
Shane Gill
581e4b940c [AltPlugin] Supports macOS Monterey 12.3 2022-02-08 18:24:10 -08:00
Riley Testut
b351558c36 [AltServer] Updates app version to 1.5b9 2021-12-15 12:49:05 -08:00
Riley Testut
d64a675e6a [AltServer] Updates AltPlugin to 1.8 2021-12-15 12:48:11 -08:00
Riley Testut
cc875a1d0b [AltPlugin] Updates version to 1.8 2021-12-15 12:47:53 -08:00
Riley Testut
a25f8fb3c0 [AltPlugin] Supports macOS Monterey 12.1 2021-12-15 12:47:11 -08:00
Riley Testut
827c0a5ea1 [Apps] Updates Delta to 1.3.1 2021-12-15 12:05:00 -08:00
Riley Testut
8f6f67cdc4 [Apps] Updates Delta beta to 1.3.1b 2021-11-17 17:10:13 -08:00
Riley Testut
dd8a6f209a [AltServer] Fixes "Sideload .ipa" file picker not appearing in foreground 2021-11-10 11:44:08 -08:00
Riley Testut
6d831f3d12 [AltServer] Hides "Sideload .ipa" menu item by default
Manual sideloading is intended to be a fallback for situations where AltStore cannot be used. To emphasize this, we hide the option by default unless the user holds the Option key.
2021-11-10 11:42:32 -08:00
Riley Testut
d393b6326c [Apps] Updates AltStore + beta to 1.4.8 and 1.5b3 2021-11-10 11:36:14 -08:00
Riley Testut
d5c7fd0869 Supports installing Fugu14-based jailbreaks on iOS 14.3 2021-10-26 11:21:44 -07:00
Riley Testut
790329f527 Fixes incorrectly signing Fugu14 app 2021-10-26 11:17:36 -07:00
Riley Testut
96c6cdc9c7 Adds basic error handling when downloading OTA firmware 2021-10-25 23:13:25 -07:00
Riley Testut
658ddebf46 Updates app version to 1.4.7 2021-10-25 22:36:09 -07:00
Riley Testut
8e726e7d70 Supports installing Fugu14-based jailbreaks
If a jailbreak app contains the relevant Fugu14 entries in its Info.plist, AltStore will automatically guide the user through the Fugu14 untether process before installing the jailbreak.
2021-10-25 22:27:30 -07:00
Riley Testut
9364434a47 [AltServer] Fixes incorrect OpenSSL header paths 2021-10-25 21:48:14 -07:00
Riley Testut
ea132a0ae8 Fixes not removing manually sideloaded .ipa's 2021-10-25 21:46:19 -07:00
Riley Testut
99d2156336 Limits “Enable JIT” context menu action to BETA builds 2021-10-25 21:43:50 -07:00
Riley Testut
a604c6d9ec [AltServer] Caches iOS and tvOS DeveloperDiskImages to separate locations
Previously, DeveloperDiskImages were only differentied by OS version, which meant (for example) iOS 15.0 and tvOS 15.0 disk images were cached to the same location. Now iOS and tvOS disk images are stored separately, so AltServer will no longer try to use a cached disk image for the wrong OS.
2021-10-13 12:55:07 -07:00
Riley Testut
3a92d30766 [Apps] Updates AltStore beta to 1.5b2 2021-10-12 13:30:40 -07:00
Riley Testut
d66556e35c [AltServer] Updates AltPlugin to 1.7 2021-10-12 12:32:43 -07:00
Riley Testut
6c8804ffb4 [AltPlugin] Updates version to 1.7 2021-10-12 12:15:42 -07:00
Riley Testut
5863ddb6e5 [AltPlugin] Supports macOS 12.0 Monterey beta 9 2021-10-12 12:14:05 -07:00
Riley Testut
8d2fbf3744 [AltWidget] Fixes not updating when app is near/past expiration 2021-10-12 12:10:45 -07:00
Riley Testut
c5f4bf71e2 [AltWidget] Improves layout on smaller devices
Shrinking app icon to 40% width allows whitespace between the app name and the "Expires in" text on smaller devices, such as the iPhone 12/13 mini.
2021-10-12 11:37:36 -07:00
Riley Testut
0cf62636a1 Fixes incorrect Settings tab bar badge color 2021-10-11 13:49:11 -07:00
Riley Testut
f7e2cf2a65 Revert "[AltWidget] Waits until the following day to reload timeline if an error occurs"
iOS automatically determines how often to refresh widgets based on user's behavior, so we should rely on that instead of artificially delaying timeline reloads until the next day if an error occurs.

This reverts commit ec8ef743a3.
2021-10-06 12:21:01 -07:00
Riley Testut
3914803157 Fixes Settings tab bar appearance on iOS 15 2021-10-06 12:16:47 -07:00
Riley Testut
f0a48a70ed Supports iOS Simulator on Apple Silicon Macs 2021-10-05 14:46:55 -07:00
Riley Testut
ec8ef743a3 [AltWidget] Waits until the following day to reload timeline if an error occurs 2021-10-04 18:41:50 -07:00
Riley Testut
44ec11f54f [AltWidget] Fixes app name appearing very small on iOS 15
Also improves layout on smaller devices, such as the iPhone 12/13 mini.
2021-10-04 17:59:41 -07:00
Riley Testut
8f404ab594 [AltWidget] Fixes incorrect days until expiration
Previously, we used Date() to calculate the number of days until apps expired. This meant all calculations were based on when the widget extension was run — not when it was actually displayed. As a result, this made it seem like the widget never updated since all timeline entries were calculated from the same date.

Now, we instead calculate remaining days from AppEntry.date. This means the widget’s remaining days are relative to the current timeline entry’s date, matching what is displayed in AltStore.
2021-10-04 17:51:14 -07:00
Riley Testut
98be5fafdf Correctly handles RefreshAllIntent exceeding time limit
• Responds with “inProgress” status if exceeding time limit
• Displays native AltStore notification only if time limit is exceeded
2021-10-04 16:27:00 -07:00
Riley Testut
47065eb2aa Embed ALTServerID in Info.plist if app uses AltKit 2021-10-04 16:06:32 -07:00
Riley Testut
00488bdde0 Fixes invalid code signature on iOS 15.1 2021-10-04 16:02:20 -07:00
Riley Testut
9d9c803d56 [Apps] Updates AltStore beta to 1.5b 2021-10-04 16:01:22 -07:00
Riley Testut
7e089359d5 [AltServer] Fixes potential crash due to race condition when device is disconnected
Simultaneously updating WiredConnectionHandler.notificationConnections can cause a crash, so we enforce serial access to notificationConnections via DispatchQueue.
2021-10-04 15:57:23 -07:00
Riley Testut
971199114b Prefers revoking existing AltStore certificate (if it exists) 2021-10-04 15:51:58 -07:00
Riley Testut
ac491b6fd1 [AltServer] Prefers revoking existing AltStore certificate (if it exists) 2021-10-04 15:51:16 -07:00
Riley Testut
c2602e05d1 [AltServer] Fixes duplicate "Revoke Development Certificate" alerts 2021-10-04 15:43:31 -07:00
Riley Testut
a685f8257b [AltServer] Fixes not ignoring InstallationError.cancelled when installing app
Allows InstallationError to be bridged back from NSError, which lets us match InstallationError against NSError's via pattern matching.
2021-10-04 15:36:16 -07:00
Riley Testut
578bee34e0 Changes "free developer accounts" to "non-developer Apple IDs" in app copy
Will hopefully clarify that the 3 active app limitation is due to using a non-developer Apple ID, and not because they haven't donated via Patreon.
2021-10-04 15:29:10 -07:00
Riley Testut
389e56e8b9 Improves ALTServerError.maximumFreeAppLimitReached error message
AltServer once again displays the list of installed sideloaded apps in error alert.
2021-10-04 15:21:57 -07:00
Riley Testut
f1bed333ba [AltServer] Fixes fetching anisette data on macOS 10.14 Mojave
AltXPC does not work on Mojave, so only attempt it on macOS 10.15 Catalina or later.
2021-10-04 13:21:24 -07:00
Riley Testut
dc7b8caae2 Updates app version to 1.5b 2021-09-15 14:31:36 -07:00
Riley Testut
93af5ec8e5 Cancels AltBackup installation if error has already been thrown 2021-09-15 14:29:37 -07:00
Riley Testut
7040d6de42 Asks user to deactivate an app when installing app without available active slot
When attempting to install a new app without any active slots available, AltStore will now present an alert asking the user to choose an app to deactivate in order to continue installation — just like when activating an app without an active slot.
2021-09-15 14:27:16 -07:00
Riley Testut
80c6a062f1 Improves error message when authenticating with invalid anisette data
A common reason anisette data is invalid is because the host computer's date & time settings are off, so now we ask the user to check their computer's date & time in the localized recovery suggestion.
2021-09-13 15:27:40 -07:00
Riley Testut
a490cf0ad2 [AltServer] Fixes incorrectly parsing thread state as decimal value when enabling JIT
Thread state is hexadecimal, so we now explicitly use NSScanner to parse string as base-16.
2021-09-13 14:15:52 -07:00
Cameron Bates
7563896f5c [AltServer] Include ALTDeviceID on install of apps (#822)
* Add ALTDeviceID to plist file on install
* Only add if deviceid key is present

Co-authored-by: Cameron Bates <cameronbates@camerons-mbp-2.lan>
2021-09-13 14:11:53 -07:00
Riley Testut
3fe6b06387 Adds "Enable JIT" context menu action for active apps
Allows users to manually enable JIT for apps that don't explicitly support AltKit.
2021-09-03 13:57:15 -05:00
Riley Testut
c8be356102 [AltServer] Supports processName in EnableUnsignedCodeExecutionRequest
Process names will be used as a fallback if the processID cannot be determined, such as when enabling JIT for another app from within AltStore.
2021-09-02 16:03:21 -05:00
Riley Testut
bd527defa5 Adds "Open" context menu action for active apps
Launches the sideloaded app by opening the app-specific URL scheme embedded by AltStore during resigning.
2021-09-02 15:52:59 -05:00
Riley Testut
967ba86e96 Fixes AppViewController navigation bar + tab bar appearance on iOS 15 2021-09-02 14:49:51 -05:00
Riley Testut
1ff3395cfb Replaces ALTDeviceID Info.plist entry (if it exists) with correct UDID when resigning apps
Allows apps to use AltKit, which needs to know the current device's UDID to communicate with AltServer.
2021-09-01 16:48:31 -05:00
Riley Testut
8cfe693f27 [AltServer] Updates app version to 1.5b7 2021-09-01 12:34:36 -05:00
Riley Testut
6347ae13e7 [AltServer] Updates AltPlugin to 1.6 2021-09-01 12:28:53 -05:00
Riley Testut
742756e99b [AltPlugin] Updates version to 1.6 2021-09-01 12:06:36 -05:00
Riley Testut
bb59e5a63a [AltPlugin] Supports macOS 12.0 Monterey beta 6 2021-09-01 12:05:18 -05:00
Riley Testut
a61910dfc5 [AltServer] Migrates LaunchAtLogin dependency from Carthage to SwiftPM
Fixes compiling AltServer on ARM Macs.
2021-09-01 11:58:33 -05:00
Riley Testut
fa31505244 [Pods] Updates AppCenter to 4.2.0
Allows compiling AltStore for iOS simulator from an ARM Mac.
2021-07-21 13:20:14 -07:00
Riley Testut
96c0f3975b [Apps] Updates AltStore to 1.4.6 2021-07-20 14:04:20 -07:00
Riley Testut
4ad521bd69 Updates app version to 1.4.6 2021-07-20 14:04:11 -07:00
Riley Testut
ce46010ec5 [Apps] Updates AltStore beta to 1.4.6b 2021-07-07 14:04:11 -07:00
Riley Testut
da72990a6d [AltServer] Updates app version to 1.5b6 2021-07-07 13:56:55 -07:00
Riley Testut
65e7037e41 [AltServer] Fixes enabling JIT on iOS 15 beta 2
vAttachName sporadically fails on iOS 15 beta 2, so we now use vAttachOrWait and manually detect whether the app is already running or not.
2021-07-07 13:54:41 -07:00
Riley Testut
fa124acb1e [AltServer] Fixes isDeveloperDiskImageMountedForDevice()
Previously, we returned YES when there was no error. Instead, we should return YES only when there’s no error _and_ the developer disk image is installed.
2021-06-24 12:56:44 -07:00
Riley Testut
955134985e Updates app version to 1.4.6b 2021-06-14 12:31:40 -07:00
Riley Testut
7779d10a9d [AltServer] Updates app version to 1.5b5
Skipping 1.5b4 to align version number with Windows AltServer (which did have a 1.5b4)
2021-06-14 12:20:39 -07:00
Riley Testut
f7b9cd4032 [AltServer] Updates AltPlugin to 1.5 2021-06-14 12:10:35 -07:00
Riley Testut
a5486004ed [AltPlugin] Updates version to 1.5 2021-06-14 12:03:05 -07:00
Riley Testut
56cfdbdcd2 [AltPlugin] Supports macOS 12.0 beta 1 2021-06-14 12:01:37 -07:00
Riley Testut
35433f0053 Fixes “unsupported code signature version” error on iOS 15 2021-06-11 11:36:30 -07:00
Riley Testut
f0a7d39a3e [AltServer] Updates AltPlugin to 1.4 2021-06-04 15:07:06 -07:00
Riley Testut
3afebc0325 [AltPlugin] Updates version to 1.4 2021-06-04 15:07:02 -07:00
Riley Testut
ccda9386e0 [AltPlugin] Supports macOS 11.4 2021-06-04 15:06:58 -07:00
Riley Testut
40e4ccd270 [AltServer] Updates AltStore download URLs to use Cloudflare CDN
Workaround for Xfinity blocking connections to f000.backblazeb2.com for some users.
2021-06-04 15:06:53 -07:00
Riley Testut
a86f3264fb [AltServer] Updates LaunchAtLogin dependency 2021-06-04 15:06:38 -07:00
Riley Testut
590cf32006 [AltServer] Improves error messages 2021-06-04 14:57:32 -07:00
Riley Testut
8b8b32b759 [AltServer] Handles EnableUnsignedCodeExecutionRequest
Allows sideloaded apps to connect to AltServer and enable JIT execution.
2021-06-04 14:57:32 -07:00
Riley Testut
9284db5d16 [AltServer] Adds “Enable JIT” menu option
Allows user to enable JIT execution for any sideloaded app by starting (and immediately stopping) a debug session on device.
2021-06-04 14:57:32 -07:00
Riley Testut
d58cbc9c3f [AltServer] Adds method to fetch installed apps on devices 2021-06-04 14:57:32 -07:00
Riley Testut
6a64568db2 [AltServer] Refactors common NSMenu logic into MenuController 2021-06-04 14:56:27 -07:00
Riley Testut
37e7125632 [AltServer] Adds ALTDebugConnection to “debug” sideloaded apps
Allows AltServer to programmatically enable JIT execution in sideloaded apps.
2021-06-04 14:56:27 -07:00
Riley Testut
c2d7714272 [AltServer] Installs Developer disk image before installing AltStore
Allows AltServer to programmatically initiate a debug session with AltStore, which can be used to start a background refresh or enable JIT on demand.

[AltServer] Renames ALTDevice variable name
2021-06-04 14:56:27 -07:00
Riley Testut
43a25da405 [AltServer] Reads devices’ OS version during discovery 2021-06-04 14:55:50 -07:00
Riley Testut
a39abe104c [AltServer] Adds methods to detect + install Developer disk images on devices 2021-06-04 14:55:50 -07:00
Riley Testut
4591563ad2 [AltServer] Adds ALTServerConnectionError to wrap libimobiledevice errors 2021-06-04 14:55:50 -07:00
Riley Testut
630c066f42 [AltServer] Updates libimobiledevice dependency 2021-06-04 14:55:06 -07:00
Riley Testut
5f9e88335c [Apps] Updates Delta to 1.3 2021-05-19 16:03:29 -07:00
Riley Testut
c021c03b8e [Apps] Updates AltStore to 1.4.5 2021-03-18 12:23:47 -07:00
Riley Testut
39221661ff Updates app version to 1.4.5 2021-03-17 13:19:44 -07:00
Riley Testut
67e85b7b25 [Apps] Updates AltStore beta to 1.4.5b 2021-03-09 14:35:01 -06:00
Riley Testut
122d039627 Merge branch '1.4.5' into develop 2021-03-09 14:32:05 -06:00
Riley Testut
4b7020d8cd Updates app version to 1.4.5b 2021-03-09 13:41:17 -06:00
Riley Testut
5394d2477a Fixes potential crash after failing to activate an app 2021-03-09 13:39:34 -06:00
Riley Testut
49b36c4847 Fixes (de-)activating apps deadlock
7278f75c made all serial operations execute in FIFO order. This caused circular dependencies between BackupAppOperation and InstallAppOperation, resulting in (de-)activating apps never finishing.

Now, we ensure InstallAppOperations that are reinstalling AltStore always execute last in a context, but other serial operations may run in any order they become ready.
2021-03-09 13:13:23 -06:00
Riley Testut
24139e7658 [Apps] Updates AltStore to 1.4.4 2021-03-05 17:28:58 -06:00
Riley Testut
ce1896dfa0 [Apps] Updates AltStore beta to 1.4.4b 2021-03-05 12:28:38 -06:00
Riley Testut
4a5fa7fd20 [Apps] Updates AltStore beta to 1.4.4b 2021-03-03 17:23:56 -06:00
Riley Testut
515b1cf23a Merge branch '1.4.4' into develop 2021-03-03 17:22:44 -06:00
Riley Testut
09c9d6380f Fixes serial operations not running in FIFO order
Ensures AltStore is always the last app to be refreshed, which matters when AltStore needs to be resigned and reinstalled (such as when using AltDaemon on iOS 14).
2021-03-01 12:45:57 -06:00
Riley Testut
7278f75c91 Limits iOS 14 AltDaemon workaround to only when using AltDaemon
Refreshing with AltServer while jailbroken will now continue using provisioning profiles, but AltDaemon will still reinstall apps instead of just refreshing them.
2021-03-01 12:41:33 -06:00
Riley Testut
ca88262be4 Updates app version to 1.4.4 2021-02-26 21:08:31 -06:00
Riley Testut
38101691de Fixes AltDaemon untrusting apps on iOS 14
Refreshing with provisioning profiles causes apps to become untrusted on iOS 14 or later. As a (hopefully) temporary workaround, we instead now always re-install apps to refresh them on iOS 14+ jailbroken devices, which does still work as expected.
2021-02-26 21:08:10 -06:00
Riley Testut
165f31be7c [Apps] Updates Delta beta to 1.3b4 2021-02-26 16:47:44 -06:00
Riley Testut
d540ce6f42 Downloads app dependencies listed in AltStore.plist
Allows apps to download additional dependencies before installation, such as plug-ins.
2021-02-26 16:47:33 -06:00
Riley Testut
7bed68f331 Adds ALTLocalizedError.underlyingError
Allows for easily wrapping underlying errors while preserving localized descriptions.
2021-02-26 15:25:12 -06:00
Riley Testut
af8d1b1969 Moves minimum iOS version check to VerifyAppOperation 2021-02-26 15:25:12 -06:00
Riley Testut
b9805b1b95 Renames ALTLocalizedError.errorFailure to failure
Better matches LocalizedError’s failureReason, recoverySuggestion, and helpAnchor naming.
2021-02-26 15:25:10 -06:00
Riley Testut
e99d5c3373 [Apps] Updates AltStore to 1.4.3 2021-02-02 13:53:20 -06:00
Riley Testut
5ac2383743 [AltServer] Updates app version to 1.5b3 2021-02-02 12:30:49 -06:00
Riley Testut
4682a1a4c4 Updates app version to 1.4.3b2 2021-02-02 12:28:15 -06:00
Riley Testut
1ba5cfdafa [Apps] Updates AltStore beta to 1.4.3b2 2021-02-02 12:26:43 -06:00
Riley Testut
e04ed39fdb [Apps] Updates Delta beta to 1.3b3 2021-02-02 12:25:51 -06:00
Riley Testut
251562ab57 Fixes apps crashing for some Apple IDs
Dynamically chooses whether to use new or old WWDR certificate when signing apps.
2021-02-01 20:45:09 -06:00
Riley Testut
9d31758026 Fixes apps crashing on iOS 13
AltSign’s updated apple.pem did not contain the Apple Root CA certificate, which caused apps to crash on iOS 13. Now both the Root CA and updated WWDR certificates are included with AltSign.
2021-02-01 16:59:18 -06:00
Riley Testut
cda73d8088 Fixes “App Group does not exist” error 2021-02-01 16:53:51 -06:00
Riley Testut
dd6ad97f4b [AltServer] Changes AltStore download URL for BETA builds 2021-02-01 13:52:23 -06:00
Riley Testut
4a002ca700 [AltServer] Updates AltPlugin to 1.3 2021-01-30 13:17:12 -06:00
Riley Testut
cafd3d19d6 [AltPlugin] Updates version to 1.3 2021-01-30 13:04:22 -06:00
Riley Testut
592dc8c58a [AltPlugin] Supports macOS 11.2 2021-01-30 13:01:37 -06:00
Riley Testut
c77dfa14d4 Fixes apps crashing due to outdated WWDR intermediate certificate
As of January 28, 2021, Apple began signing provisioning profiles with a new WWDR intermediate certificate. This broke all apps installed with AltStore after that date, but updating our local certificate to match Apple’s fixes the issue.
2021-01-29 15:29:10 -06:00
Riley Testut
6dd8330cb8 [AltServer] Fixes missing embedded certificate when using cached certificate 2020-12-17 14:45:03 -06:00
Riley Testut
8cec603921 Adds Clip 1.1a1 to apps-alpha.json 2020-12-08 12:29:14 -06:00
Riley Testut
2a84551232 Fixes widget not updating after refreshing AltStore 2020-12-07 16:13:10 -06:00
Riley Testut
49cd23324d Updates pods, fixes Sparkle on Apple Silicon Macs 2020-12-03 16:33:40 -06:00
Riley Testut
081ebf3988 Merge branch 'develop' of github.com:rileytestut/AltStore into develop 2020-12-03 16:25:32 -06:00
Riley Testut
51aca00553 [AltServer] Works without Mail plug-in if SIP and AMFI are both disabled 2020-12-03 16:24:43 -06:00
Riley Testut
954c18ad2f [AltXPC] Initial version
AltXPC uses the private com.apple.authkit.client.internal entitlement to retrieve anisette data.
2020-12-03 16:24:43 -06:00
Riley Testut
e5464f419c [AltPlugin] Refactors anisette data retrieval into public method 2020-12-03 16:24:43 -06:00
Riley Testut
a5dbaf97e4 [AltServer] Updates AltPlugin to 1.2 2020-12-03 16:24:43 -06:00
Riley Testut
c0c2e3c78e [AltPlugin] Updates version to 1.2 2020-12-03 16:24:42 -06:00
Riley Testut
4d5e5cec70 Updates apps.json and apps-alpha.json 2020-12-03 16:19:07 -06:00
Riley Testut
57672b9d2f Prefers paid developer teams over free teams 2020-12-03 16:06:04 -06:00
Riley Testut
4863a15f35 [AltServer] Prefers paid developer teams over free teams 2020-12-03 16:06:04 -06:00
Riley Testut
2309367843 [AltServer] Supports multiple devices with same Apple ID
AltServer now caches certificates for each Apple ID used to install AltStore, and will re-use them for future installations rather than revoke + create new ones each time (if possible).
2020-12-03 16:06:04 -06:00
Riley Testut
ba5f2b6186 [AltServer] Fixes “RSTPlaceholderView.nib couldn’t be saved” error 2020-12-03 16:06:04 -06:00
Riley Testut
1448616f77 Updates apps.json with Delta 1.3b1 2020-12-03 16:06:04 -06:00
Riley Testut
2dfb740f77 [AltServer] Supports sideloading apps to Apple TV 2020-12-03 16:06:04 -06:00
Riley Testut
0205c3e969 Updates AltSign dependency 2020-12-03 16:06:03 -06:00
Riley Testut
98efadd388 [AltPlugin] Supports macOS 11.1 2020-12-02 14:29:22 -06:00
Riley Testut
de2ec2814c [AltServer] Supports sideloading .ipa files directly to iOS devices 2020-11-11 17:40:28 -08:00
Riley Testut
425425b64c [AltServer] Fixes wireless devices not appearing in devices list 2020-11-11 16:38:45 -08:00
Riley Testut
f3ece1794e Resolves conflicts with master branch
# Conflicts:
#	AltStore/Model/DatabaseManager.swift
#	Shared/Extensions/Bundle+AltStore.swift
2020-11-11 13:05:16 -08:00
Riley Testut
67c527adfb Updates app version to 1.4.2 2020-11-11 12:53:31 -08:00
Riley Testut
a28457064e Updates apps.json with AltStore 1.4.2 2020-11-11 12:52:36 -08:00
Riley Testut
5b9cc1657c Updates apps.json with AltStore 1.4.2b1 2020-11-11 11:41:27 -08:00
Riley Testut
3777644ee0 Updates apps-alpha.json with AltStore 1.4.2a1 2020-11-05 10:59:08 -08:00
Riley Testut
99e0b7c275 Merge branch 'develop' of https://github.com/rileytestut/AltStore into develop 2020-11-03 14:10:09 -08:00
Riley Testut
ca21c1097a Fixes JIT on iOS 14.2+
Updates code signature version to 0x20400 which allows apps to use JIT on iOS 14.2 and later.
2020-11-03 14:10:03 -08:00
osy86
d96e2534cc Hide private entitlements on >= iOS 13.5 (#415)
iOS 13.5 fixes the psychic paper hack so showing the private entitlement
warning popup is confusing to the user. Additionally iOS 14 checks the
entitlements on installation, so we should not copy the private entitlements
on iOS 14.

Depends on https://github.com/rileytestut/AltSign/pull/15

Co-authored-by: osy <osy86@users.noreply.github.com>
2020-11-03 14:02:19 -08:00
Riley Testut
6eae28d961 Updates apps-alpha.json 2020-10-26 15:59:19 -07:00
Riley Testut
d4d23d39e5 Updates apps.json 2020-10-26 14:49:35 -07:00
Riley Testut
31f70488ad [AltServer] Updates app version to 1.4.1 2020-10-26 14:48:53 -07:00
Riley Testut
2511f12a0a [AltServer] Fixes keyboard shortcuts in NSAlert text fields
Partially reverts commit 1b1c1922 and adds back the “un-used” app main menu in Main.storyboard, which broke keyboard shortcuts in alerts when removed.
2020-10-26 12:14:42 -07:00
Riley Testut
1a4c14db1f [AltServer] Supports installing apps with app extensions 2020-10-15 11:37:58 -07:00
Riley Testut
d5446f1d38 Fixes iOS 14.2 crash-on-launch due to invalid code signature 2020-10-15 11:11:00 -07:00
Riley Testut
e1dce102a0 Removes “Install AltDaemon” option from Settings tab
AltDaemon can now be installed directly from the Dynastic repo via Cydia or Sileo.
2020-10-07 11:32:47 -07:00
Riley Testut
0da41ac767 Merge branch 'update_plugin' of https://github.com/rileytestut/AltStore into develop 2020-10-06 18:16:09 -07:00
Riley Testut
9d7c92ad08 [AltDaemon] Updates version to 1.0 2020-10-06 18:14:41 -07:00
Riley Testut
220a7233ca [AltServer] Adds PluginManager to update Mail plug-in to 1.1
AltPlugin 1.1 supports Big Sur on both Intel and Apple Silicon Macs.
2020-10-06 18:11:03 -07:00
Riley Testut
150c90563b [AltPlugin] Supports Big Sur on both Intel and Apple Silicon Macs 2020-10-06 18:09:47 -07:00
Riley Testut
b4230baedf Updates app version to 1.4 2020-10-05 14:57:22 -07:00
Riley Testut
6910a717eb Limits adding sources to allowed identifiers in non-BETA builds 2020-10-05 14:48:48 -07:00
Riley Testut
aef3a2775d Adds @Managed property wrapper
• Keeps strong reference to wrapped managed object’s context.
• Projected value simplifies accessing properties on context’s thread.
2020-10-05 14:23:19 -07:00
Riley Testut
f9cf3cb65b Limits “Change App Icon” option to BETA builds for now 2020-10-05 13:59:44 -07:00
Riley Testut
636ca78cbf [AltServer] Updates LaunchAtLogin dependency to 3.0.2 2020-10-05 13:58:13 -07:00
Riley Testut
ba79c5c5c0 Cleans up AltStore Xcode scheme 2020-10-05 13:56:59 -07:00
Riley Testut
12fa9a1c72 Merge branch 'develop' of https://github.com/rileytestut/AltStore into develop 2020-10-01 14:14:46 -07:00
Riley Testut
f2cd9d018e Migrates from Core Data model v8 to v9 2020-10-01 14:14:17 -07:00
Riley Testut
9185552432 [Shared] Fixes generic error messages when refreshing 2020-10-01 14:09:46 -07:00
Riley Testut
3eb2d72292 Updates KeychainAccess pod 2020-10-01 14:09:46 -07:00
Riley Testut
19bf4355ee Adds ability to change sideloaded app icons 2020-10-01 14:09:45 -07:00
Riley Testut
6ec3e73189 Adds InstalledApp.needsResign
When true, app will be resigned + reinstalled next refresh rather than just refreshing provisioning profiles.
2020-10-01 11:52:26 -07:00
Riley Testut
a32e23fd99 [AltDaemon] Updates version to 0.4 2020-09-30 15:05:32 -07:00
Riley Testut
3f5470dc2f [AltDaemon] Fixes XPC service lookup for Odyssey jailbreak 2020-09-30 15:04:44 -07:00
Theodore Dubois
700033aa2c Add https:// to a source URL if you forget the scheme (#361) 2020-09-27 13:56:54 -07:00
Riley Testut
110f4eacda Fixes serverNotFound error when refreshing apps due to background fetch
Extends time limit for discovering servers back to 3 seconds, and now accounts for wired and local servers.
2020-09-24 13:01:31 -07:00
Riley Testut
fe5928aaa4 [AltServer] Fixes crash when reading some provisioning profiles from device 2020-09-24 13:00:25 -07:00
Riley Testut
b7952fbb79 Fixes crash when reading some provisioning profiles from device 2020-09-23 11:47:47 -07:00
Riley Testut
060681493d [AltDaemon] Updates version to 0.3 2020-09-22 15:26:20 -07:00
Riley Testut
3145c8248e [AltDaemon] Replaces local socket communication with XPC
Allows AltDaemon to be launched on demand + reject any connections not made from AltStore.
2020-09-22 15:12:33 -07:00
Riley Testut
719e8e4924 Merge branch 'widget' into develop 2020-09-22 14:57:46 -07:00
Riley Testut
da57f37549 [AltWidget] Fixes certain app icons not appearing 2020-09-22 10:53:18 -07:00
Riley Testut
2a7f0e674c [AltWidget] Fixes green tinting for sideloaded apps
Changes fallback tint color from AltStore-green to gray, which is more neutral.
2020-09-22 10:48:43 -07:00
Riley Testut
fb938437cf [AltWidget] Preserves layout if app icon is missing 2020-09-22 10:45:13 -07:00
Riley Testut
5e17b1a3ff Migrates database + cached apps from app sandbox to app group 2020-09-16 12:09:12 -07:00
Riley Testut
3a25300e2c [AltWidget] Fixes crash when featured app is expired 2020-09-15 15:22:10 -07:00
Riley Testut
530cec3fa6 [AltWidget] Allows choosing featured app 2020-09-15 15:19:12 -07:00
Riley Testut
f37c167596 [AltWidget] Initial version 2020-09-15 14:27:22 -07:00
Riley Testut
4085a11539 Revises AltSign dependency graph
AltStoreCore now links AltSign-Static, so no need to also link against AltSign-Dynamic from other targets.
2020-09-14 15:57:15 -07:00
Riley Testut
8e865fbd52 [AltDaemon] Removes Roxas pod 2020-09-14 15:33:46 -07:00
Riley Testut
a02651ca5a Provides fallback error when connecting to AltServer
Fixes operation never finishing under certain circumstances.
2020-09-14 14:34:45 -07:00
Riley Testut
8716da325d Moves database + cached apps to app group so they can be accessed by extensions 2020-09-14 14:31:46 -07:00
Riley Testut
b560c09aa6 [AltStoreCore] Adds Date, FileManager, and UIColor extensions 2020-09-14 14:18:15 -07:00
Riley Testut
324f480f04 Updates apps.json and apps-alpha.json 2020-09-14 11:25:41 -07:00
Riley Testut
bd5394c927 [AltStoreCore] Sets APPLICATION_EXTENSION_API_ONLY to YES 2020-09-10 11:45:11 -07:00
Riley Testut
d528a7940d Refines AppManager Combine pipeline 2020-09-10 11:27:44 -07:00
Riley Testut
3e5a38977b Merge branch 'module_refactoring' into develop 2020-09-09 10:41:17 -07:00
Riley Testut
23c773dc17 Fixes incorrect Background Refresh cell style pre-iOS 14 2020-09-08 17:11:22 -07:00
Riley Testut
3f9700e17f Adds button to add Refresh All Apps shortcut to Siri 2020-09-08 16:44:36 -07:00
Riley Testut
4eade601c8 Fixes opening deep links 2020-09-08 16:42:25 -07:00
Riley Testut
7f1968f8c2 Extends additional intent handling time to 9 seconds 2020-09-08 16:40:07 -07:00
Riley Testut
0ca7c6fb0e Replaces AltStore(Core) Roxas pod with framework
Fixes compilation errors when archiving app.
2020-09-08 13:44:08 -07:00
Riley Testut
987bf17bb1 Adds new Core Data model v8
No need for explicit migration/mapping model (yet) because we only added a transient property.
2020-09-08 13:28:59 -07:00
Riley Testut
a828e59715 Updates UI when refreshing apps with Siri 2020-09-08 13:12:40 -07:00
Riley Testut
d9ebf56827 Fixes incorrect app subtitles in Browse tab 2020-09-08 13:00:01 -07:00
Riley Testut
6cfe9cfdad Supports refreshing apps with Siri on iOS 14 2020-09-08 12:29:44 -07:00
Theodore Dubois
7f68b808c8 Fix file providers (#346)
* Make file providers work at all

NSExtensionFileProviderDocumentGroup must be a valid app group. This
updates it to use the new name of the app group including the team ID.

* Update AltStore/Operations/ResignAppOperation.swift
2020-09-04 16:29:01 -07:00
Theodore Dubois
1b1c1922fe Make AltServer menu appear attached to the icon (#55)
* Make AltServer menu appear attached to the icon
* Update AltServer/AppDelegate.swift
2020-09-04 13:22:26 -07:00
Riley Testut
cc6d9d1842 Switches to UIScene-based lifecycle 2020-09-03 16:58:56 -07:00
Riley Testut
472de7dd21 [AltStoreCore] Refactors core AltStore logic into AltStoreCore framework
AltStoreCore will contain all shared AltStore code between AltStore and any app extensions. Initially, it includes all AltStore model logic.
2020-09-03 16:39:08 -07:00
Riley Testut
84cdc2dfa8 Replaces AltSign cocoapod with Swift package 2020-09-03 16:02:28 -07:00
Riley Testut
7bd93bba2d [AltKit] Replaces dedicated AltKit module with shared files across targets
Treating AltKit as a full module resulted in more complexity than necessary, when we really just wanted to share some files between different targets. Now we can share individual files across modules as-needed without AltKit overhead.
2020-09-03 15:35:29 -07:00
osy
97232fec1e Preserve device specific keys in Info.plist
Apple's Info.plist support platform and device specific keys to augment existing
keys. For example `UISupportedInterfaceOrientations~ipad` replaces
`UISupportedInterfaceOrientations` when running on an iPad.

By using Bundle.infoDictionary, Apple will pre-process the Info.plist and replace
any key with its device specific variant. Since AltStore does not support iPad,
this will strip out any iPad specific keys for the installing app.

We add an extension Bundle.completeInfoDictionary that will return the original
de-serialized dictionary including all the device specific keys.

See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html#//apple_ref/doc/uid/TP40009254-SW9

# Conflicts:
#	AltKit/Extensions/Bundle+AltStore.swift
#	AltStore/Model/DatabaseManager.swift
2020-08-31 12:47:11 -07:00
Riley Testut
11ac99af97 Updates apps.json 2020-08-31 12:39:41 -07:00
Riley Testut
74810909e3 Merge pull request #260 from osy86/master
Preserve device specific keys in Info.plist
2020-08-31 12:37:11 -07:00
Riley Testut
fff044344a Migrates from Core Data model v6 to v7 2020-08-28 13:25:20 -07:00
Riley Testut
89f214e872 Updates apps.json for 1.3.6 2020-08-28 12:58:54 -07:00
Riley Testut
7f54953d84 Fixes missing CoreCrypto header errors for AltKit + AltServer 2020-08-28 12:43:47 -07:00
Riley Testut
b33c72959d Merge branch 'accessibility_improvements' into develop
# Conflicts:
#	AltStore/Sources/SourcesViewController.swift
2020-08-28 12:39:05 -07:00
Riley Testut
c6beba4412 Adds altstore://source?url=[link] deep link to add sources 2020-08-28 12:15:15 -07:00
Riley Testut
1b0f6a19f1 Shows source errors in SourcesViewController 2020-08-27 16:39:03 -07:00
Riley Testut
08bcd9b9fd Improves error handling when fetching multiple sources
Fetching sources is no longer all or nothing. Now if a source cannot be fetched, it won’t prevent other sources from being updated.
2020-08-27 16:28:13 -07:00
Riley Testut
1d8ef12686 Improves News tab accessibility
Combines News item name + subtitle into single accessibility group.
2020-08-27 15:27:38 -07:00
Riley Testut
ba34999c71 Improves My Apps tab accessibility 2020-08-27 15:25:52 -07:00
Riley Testut
a49c48b15b Announces errors when VoiceOver is enabled 2020-08-27 15:24:26 -07:00
Riley Testut
3e600ca2d8 Improves AppBannerView accessibility 2020-08-27 15:23:21 -07:00
Riley Testut
503709f385 Updates AltSign dependency 2020-08-27 14:40:33 -07:00
Riley Testut
ecce91d630 Updates patreon access token 2020-08-14 12:27:13 -07:00
Riley Testut
87a6e0f2d0 Updates apps.json for AltStore 1.4b4 2020-07-27 13:31:09 -07:00
Riley Testut
5daca634f4 Fixes “unsupported code signature version” error on iOS 14 2020-07-24 13:08:58 -07:00
Riley Testut
af6f57f945 Fixes installing AltStore versions containing app extensions 2020-07-24 13:02:48 -07:00
Riley Testut
f683cd0fc6 [AltServer] Uses actual app bundle ID when installing app 2020-07-24 12:21:42 -07:00
Riley Testut
a4d7bb75ff Updates apps.json for 1.3.5 2020-07-15 14:28:54 -07:00
Riley Testut
b6c1b51a44 Merge branch '1.3.5' into develop 2020-07-15 14:28:06 -07:00
Riley Testut
47a9c3229e Updates app version to 1.3.5 2020-07-15 11:58:46 -07:00
Riley Testut
b3c82cf054 Fixes Bonjour discovery on iOS 14
iOS 14 requires apps to specify which Bonjour services they support as well as a usage description in order to browse the local network.
2020-07-15 11:55:48 -07:00
Riley Testut
e2a5b9b090 Fixes Apple ID authentication on iOS 14 and macOS 11 2020-07-15 11:55:39 -07:00
Riley Testut
98582d49ed [AltDaemon] Changes default build configuration to Release 2020-06-22 16:04:57 -07:00
Riley Testut
db959c42b8 Adds 1.4 prerelease versions to apps(-alpha).json 2020-06-22 16:03:49 -07:00
Riley Testut
23a2d57f36 Adds Clip 1.0 to apps.json 2020-06-22 16:03:08 -07:00
Riley Testut
556a2541a3 [AltDaemon] Updates version to 0.2 2020-06-11 17:57:14 -07:00
Riley Testut
e5818334a3 [AltDaemon] Disables tweak injection to improve stability 2020-06-11 16:16:37 -07:00
Riley Testut
c6a2de7115 [AltDaemon] Fixes certificate becoming untrusted after refreshing 2020-06-11 16:15:45 -07:00
osy
936b57eadf Preserve device specific keys in Info.plist
Apple's Info.plist support platform and device specific keys to augment existing
keys. For example `UISupportedInterfaceOrientations~ipad` replaces
`UISupportedInterfaceOrientations` when running on an iPad.

By using Bundle.infoDictionary, Apple will pre-process the Info.plist and replace
any key with its device specific variant. Since AltStore does not support iPad,
this will strip out any iPad specific keys for the installing app.

We add an extension Bundle.completeInfoDictionary that will return the original
de-serialized dictionary including all the device specific keys.

See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html#//apple_ref/doc/uid/TP40009254-SW9
2020-06-10 15:05:31 -07:00
Riley Testut
706e52c464 Merge branch 'jailbreak' into develop 2020-06-08 11:33:57 -07:00
Riley Testut
8883f5f55d Merge branch 'backup_apps' into develop 2020-06-08 11:33:26 -07:00
Riley Testut
e3f0359f3e Adds “Install AltDaemon” option to settings (jailbreak only)
Exports AltDaemon that can be installed with Filza or another file/package manager.
2020-06-07 10:02:41 -07:00
Riley Testut
feae50520d Replaces cached AltStore every launch for DEBUG builds 2020-06-07 09:49:29 -07:00
Riley Testut
d221c95b6f [AltServer] Includes underlying installation error in error response 2020-06-07 09:48:53 -07:00
Riley Testut
761282baf0 [AltKit] Gracefully fails if no data is received over network connection 2020-06-05 15:43:05 -07:00
Riley Testut
7a9dfdfe5e Improves error messages when there’s an underlying error 2020-06-05 15:32:10 -07:00
Riley Testut
fb3a96a511 [AltDaemon] Synchronizes AppManager operations
Installing and removing apps is now done on a serial dispatch queue, and installing/removing profiles uses file coordination.
2020-06-05 14:35:05 -07:00
Riley Testut
fb1e6e059a [AltKit] Includes underlying error in error response 2020-06-05 14:19:40 -07:00
Riley Testut
b5b3887348 [AltDaemon] Adds explicit autoreleasepool to main.swift 2020-06-05 14:13:09 -07:00
Riley Testut
4311db2cf7 Supports installing/refreshings apps w/o computer on jailbroken devices
AltStore will use AltDaemon as a local AltServer if it’s installed and running. AltStore remains a regular sandboxed app, but AltDaemon has private entitlements necessary to perform AltServer operations without a computer.
2020-06-04 19:53:10 -07:00
Riley Testut
6f075e7ac4 [AltDaemon] Initial version
AltDaemon allows AltStore to install and refresh apps without a computer on jailbroken devices. AltDaemon has the necessary entitlements to perform the same actions AltServer normally does over WiFi, and uses the same AltServer request logic to handle local requests.
2020-06-04 19:48:02 -07:00
Riley Testut
ee3a2eec35 [AltServer] Moves core ConnectionManager logic to AltKit
Refactors ConnectionManager to use arbitrary RequestHandlers and ConnectionHandlers. This allows the core AltServer request logic to be shared across different targets with different connection types.
2020-06-04 19:06:13 -07:00
Riley Testut
4cd3ede535 Updates apps.json for 1.3.4 2020-05-27 10:11:02 -07:00
Riley Testut
9318dadbd4 [AltServer] Updates app version to 1.3.2 2020-05-27 10:10:32 -07:00
Noah Keck
7245832745 Merge pull request #195 from rileytestut/noah978-add-issue-templates
Create issue templates
2020-05-23 12:22:33 -05:00
Noah Keck
b769fa94a2 Add logs to additional context 2020-05-23 12:21:30 -05:00
Noah Keck
39e9e97d3f Create issue templates 2020-05-22 16:09:02 -05:00
Riley Testut
f8dfef9f2b [AltServer] Improves error message when device is untrusted or locked during installation 2020-05-21 22:06:18 -07:00
Riley Testut
2bb47798dd [AltServer] Suggests disabling “Offload Unused Apps” in error message
iOS 13.5 counts offloaded apps as active sideloaded apps (for some reason), so improve error messages to mention this.
2020-05-21 22:04:24 -07:00
Riley Testut
f8b8381129 Updates app version to 1.3.4 2020-05-19 20:10:55 -07:00
Riley Testut
0124c60680 [AltServer] Updates app version to 1.3.1 2020-05-19 20:09:50 -07:00
Riley Testut
f6854dca3c [AltServer] Supports app groups when installing AltStore
Necessary for (de-)activation to work as expected in AltStore 1.3.4.
2020-05-19 18:30:53 -07:00
Riley Testut
c06d7d636a Adds option to explicitly back up installed apps 2020-05-19 11:47:43 -07:00
Riley Testut
1ab36874be Fixes “invalid entitlements” when refreshing AltStore
Replaces “resigned” app group ID with “base” app group ID before resigning AltStore.
2020-05-18 16:00:08 -07:00
Riley Testut
fcc1cea73a Limits new (de-)activation flow to 13.5 or later 2020-05-18 00:04:09 -07:00
Riley Testut
b2c09049be Adds altstore://install?url=[link] deep link to install remote .ipa’s 2020-05-17 23:47:26 -07:00
Riley Testut
47d206ad19 Removes active app extension limits on 13.5 or later 2020-05-17 23:47:26 -07:00
Riley Testut
1d03ead09f [AltServer] Adds wired connection reading timeout 2020-05-17 23:47:26 -07:00
Riley Testut
7cd01b97bb Uses real app icon for AltBackup icon 2020-05-17 23:47:26 -07:00
Riley Testut
c895ebb1a2 [AltBackup] UI reflects whether backup/restore/nothing is happening 2020-05-17 23:47:26 -07:00
Riley Testut
9d3621a872 Adds option to manually restore backup for active apps that have one 2020-05-17 23:47:26 -07:00
Riley Testut
b79051e247 Adds option to export backups for inactive apps 2020-05-17 23:47:26 -07:00
Riley Testut
c968f15181 Activates apps by reinstalling then restoring backup on iOS 13.5+
To activate an inactive app that has been deleted from the phone, AltStore will reinstall the app, as well as restore any app data from when it was deactivated.
2020-05-17 23:47:26 -07:00
Riley Testut
46469838f8 Deactivates apps by backing up + deleting them on iOS 13.5+
Deactivating apps by removing their profiles no longer works on iOS 13.5. Instead, AltStore will now back up the app by temporarily replacing it with AltBackup, then remove the app from the phone.
2020-05-17 23:47:26 -07:00
Riley Testut
75d1084b5f Supports removing inactive apps from My Apps 2020-05-17 23:47:26 -07:00
Riley Testut
faa93a6aa0 Adds RemoveAppBackupOperation to remove backed up app data 2020-05-17 23:47:26 -07:00
Riley Testut
407a259221 Adds RemoveAppOperation for removing inactive apps 2020-05-17 23:47:26 -07:00
Riley Testut
dafc87d899 Fixes updating App IDs with no app groups 2020-05-17 23:47:26 -07:00
Riley Testut
92781d3380 Adds BackupAppOperation to backup and restore app data 2020-05-17 23:47:26 -07:00
Riley Testut
775f0e67e1 Adds option to not cache downloaded app during installation 2020-05-17 23:47:26 -07:00
Riley Testut
a61fb32eb8 [AltBackup] Derives backup location from original bundle ID, not resigned one
Allows the backup to be used even if the app is later installed with a different developer team.
2020-05-17 23:47:26 -07:00
Riley Testut
3ac2beecfc Embeds original bundle ID under ALTBundleIdentifier Info.plist key 2020-05-17 23:47:26 -07:00
Riley Testut
0311e15d84 [AltBackup] No longer assumes AltStore app group is first in ALTAppGroups 2020-05-17 23:47:26 -07:00
Riley Testut
973cede232 Supports resigning apps with multiple app groups 2020-05-17 23:47:24 -07:00
Riley Testut
89fba9f3ad Fixes missing error descriptions when using NSError.withLocalizedFailure() 2020-05-15 14:54:46 -07:00
Riley Testut
38744a1792 Adds initial AltBackup app
When deactivating an app, AltStore will first install AltBackup in its place. This allows AltBackup to access the (soon to be) inactive app’s sandbox, and backup all files to a shared app group with AltStore. Later when activating, AltStore will again install AltBackup and use it to restore files before installing the actual app again.
2020-05-15 14:54:46 -07:00
Riley Testut
f9b1208e73 Replaces ConnectionError.errorDescription with .failureReason
Improves error messages where ConnectionError was the underlying failure, but not the main error.
2020-05-15 14:54:46 -07:00
Riley Testut
f384ac8d63 [AltServer] Uses empty strings in place of nil error messages 2020-05-15 14:54:46 -07:00
Riley Testut
7d2dad1ebc Adds additional checks before considering apps deleted 2020-05-15 14:54:46 -07:00
Riley Testut
5ac442638c Supports custom entitlements when fetching provisioning profiles 2020-05-15 14:54:43 -07:00
Riley Testut
c0b9e798c9 Fixes RefreshGroup strong reference cycle 2020-05-14 16:31:23 -07:00
Riley Testut
f5b084f93d [AltServer] Supports “remove app” requests
Improves support for removing apps
2020-05-14 16:31:23 -07:00
Riley Testut
6ba1b3fdbc [AltServer] Renames NSError+ALTServerError category methods to avoid runtime conflicts 2020-05-11 14:33:53 -07:00
Riley Testut
296af970df Updates apps.json and apps-alpha.json 2020-05-09 12:53:12 -07:00
Riley Testut
973ae7de52 Updates apps.json 2020-05-08 18:27:54 -07:00
Riley Testut
e08646a337 Fixes updating DolphiniOS due to mismatched bundle IDs
Manually sets Dolphin’s CFBundleIdentifier to match the source bundle ID to prevent breaking updates for existing users.
2020-05-08 11:45:23 -07:00
Riley Testut
d1d7398e77 Redownloads missing cached apps when refreshing or updating 2020-05-08 11:43:34 -07:00
Riley Testut
27241a8d08 Verifies app’s bundle ID matches source’s before installing
Prevents apps with incorrect bundle IDs from being installed and then deleted from disk due to AltStore thinking the apps have been removed.
2020-05-07 13:13:05 -07:00
Riley Testut
3e3246e560 Adds print statement when deleting cached apps 2020-05-07 13:10:01 -07:00
Riley Testut
19313ce5e0 Improves error when app being refreshed has been deleted 2020-05-07 13:08:52 -07:00
Riley Testut
fe2f9d5b2b Treats App ID bundle IDs as case-insensitive
Apple’s servers return an error when registering a bundle ID with different capitalization than an existing one, so we now perform case-insensitive comparisons when determining if we need to register an App ID.
2020-05-07 12:45:09 -07:00
Riley Testut
094dc0da26 Updates app version to 1.3.2 2020-05-03 14:48:23 -07:00
Riley Testut
a23368363b Asks user for permission before installing apps with private entitlements 2020-05-02 22:06:57 -07:00
Riley Testut
c11ecd2226 Merge pull request #178 from rileytestut/develop
AltStore 1.3 & 1.3.1
2020-05-01 14:47:44 -07:00
Riley Testut
54e34ade34 Updates app version to 1.3.1 2020-05-01 14:44:15 -07:00
Riley Testut
7c8c738d9f Preserves private entitlements for Psychic Paper usage
Psychic Paper allows apps to use private entitlements without jailbreaking. AltStore now preserves private entitlements and includes them when resigning to allow apps to take advantage of this. For more info, see https://github.com/Siguza/psychicpaper
2020-05-01 10:27:22 -07:00
Riley Testut
bbe08c1371 Merge branch 'feature/active_inactive_apps' into develop 2020-05-01 08:53:21 -07:00
Riley Testut
c0e2379b7e Updates apps.json & apps-alpha.json 2020-05-01 08:52:51 -07:00
Riley Testut
72f530fe82 Updates apps.json for AltStore 1.3 2020-04-10 13:53:30 -07:00
Riley Testut
4f493a1852 Adds AltStore (Stable) to Alpha source 2020-04-10 13:52:30 -07:00
Riley Testut
dc89911937 Updates app version to 1.3 2020-04-09 12:26:41 -07:00
Riley Testut
aaf108732a [AltServer] Updates app version to 1.3 2020-04-09 12:25:48 -07:00
Riley Testut
57750b9d8f Adds Alpha source JSON 2020-04-09 12:22:57 -07:00
Riley Testut
261c912b82 Updates apps.json for AltStore 1.3b3 2020-04-09 12:21:23 -07:00
Riley Testut
0b54849a8d Uses separate App Center tokens for different build types 2020-04-09 12:18:17 -07:00
Riley Testut
f995cbdeeb Disables Sources functionality for public versions 2020-04-01 13:27:26 -07:00
Riley Testut
b3b362ffc6 Enables sideloading for public versions 2020-04-01 13:26:22 -07:00
Riley Testut
618bcea16a [AltServer] Updates Carthage dependencies for Xcode 11.4 2020-04-01 13:17:17 -07:00
Riley Testut
01e73ed2f7 Leaves apps activated if there is no active app limit during migration 2020-04-01 13:06:06 -07:00
Riley Testut
05fcc3b5e0 [AltServer] Removes duplicate profiles even if they’re excluded 2020-04-01 12:19:25 -07:00
Riley Testut
5ffc0ae05b Clarifies AltStore renews App IDs after they expire 2020-04-01 11:53:25 -07:00
Riley Testut
ff5c643254 Adds support for ALPHA builds 2020-04-01 11:51:00 -07:00
Riley Testut
3fc21ebb74 Fixes potentially incorrect bundle identifier when resigning AltStore with DEBUG build 2020-03-31 14:33:13 -07:00
Riley Testut
e01e3fbbf2 Adds VS App Center analytics + crash reporting
Currently tracks install, refresh, and update app events.
2020-03-31 14:31:34 -07:00
Riley Testut
028eca09a5 Updates RefreshError.noInstalledApps’ localized description 2020-03-30 15:38:00 -07:00
Riley Testut
60deb5bcc9 Fixes RefreshAltStoreViewController never finishing 2020-03-30 15:23:20 -07:00
Riley Testut
b1a027762d Fixes potentially incorrect bundle identifier when resigning/refreshing AltStore 2020-03-30 15:18:10 -07:00
Riley Testut
59d9a0faeb [AltServer] Fixes installing outdated profile after app installation 2020-03-30 15:06:16 -07:00
Riley Testut
3010f8374c Updates older ToastView code to use error initializer 2020-03-30 14:07:18 -07:00
Riley Testut
85752abbf4 Fixes missing OperationError recovery suggestions 2020-03-30 13:56:40 -07:00
Riley Testut
7208dc18ad Adds basic search functionality to Browse tab 2020-03-30 13:46:15 -07:00
Riley Testut
21088fa7ce Removes ellipsis from AppIDsViewController cell 2020-03-30 13:40:14 -07:00
Riley Testut
dfeceeb85d Deactivates beta apps when no longer a patron/signed in
Prevents beta apps from taking up active app slots despite not being listed in My Apps
2020-03-30 13:34:13 -07:00
Riley Testut
61992380da Dismisses SFVC when sideloading apps from News item 2020-03-30 13:26:44 -07:00
Riley Testut
4f8a8faac4 Removes sideloading beta alert 2020-03-30 13:25:14 -07:00
Riley Testut
a649c40fe8 Migrates from Core Data model v5 to v6 2020-03-24 13:43:16 -07:00
Riley Testut
e5170a2f4a Adds initial support for 3rd party Sources 2020-03-24 13:27:44 -07:00
Riley Testut
f396fbee3b Deletes cached apps after they’ve been uninstalled from device 2020-03-23 12:12:49 -07:00
Riley Testut
a9199a0af2 Emphasizes App IDs can’t be deleted in AppIDsViewController message 2020-03-23 11:33:06 -07:00
Riley Testut
fd551ade2e Adds Drag & Drop support for activating/deactivating apps 2020-03-20 16:38:54 -07:00
Riley Testut
71e3f3f36a Adds option to remove app extensions before installation
Free developer accounts may only have 3 active apps and app extensions, so this option allows users to limit active slots an app will take
2020-03-20 15:56:10 -07:00
Riley Testut
045e27b048 Fixes incorrect action when refreshing/activating apps due to cell reuse 2020-03-20 15:52:11 -07:00
Riley Testut
b705e71ae0 Fixes grayed-out .ipas due to duplicate UTI declarations 2020-03-20 15:51:33 -07:00
Riley Testut
77ad15f2ce Restores peek & pop in MyAppsViewController on iOS 12 2020-03-20 15:33:29 -07:00
Riley Testut
33a7f11c83 Improves error toast view appearance 2020-03-20 15:31:20 -07:00
Riley Testut
522543b8ee Removes unused Team variable 2020-03-19 11:58:03 -07:00
Riley Testut
eee68c5b8c Fixes race condition when installing app with app groups + extensions 2020-03-19 11:56:28 -07:00
Riley Testut
d66b100e2f Fixes endless refreshing if error occurs when legacy refreshing 2020-03-19 11:53:53 -07:00
Riley Testut
d3a99f7e48 Fixes tuple unpacking warning with Xcode 11.4 2020-03-19 11:50:39 -07:00
Riley Testut
2ba39d2461 [AltServer] Updates app version to 1.3b2 2020-03-17 12:53:53 -07:00
Riley Testut
5ed9d45d90 [AltServer] Fixes installing more than 3 apps on 13.3 and below 2020-03-17 12:24:11 -07:00
Riley Testut
a049a69489 Fixes hard-to-see activity indicators in dark mode 2020-03-16 13:24:04 -07:00
Riley Testut
52485d1080 Updates app version to 1.3b 2020-03-12 10:10:11 -07:00
Riley Testut
0c7c95251b Adds BETA compilation condition by default 2020-03-12 10:09:59 -07:00
Riley Testut
e036fcc2c7 [AltServer] Updates app version to 1.3b 2020-03-12 10:05:18 -07:00
Riley Testut
959a521b8b Migrates from Core Data model v4 to v5 2020-03-11 17:29:32 -07:00
Riley Testut
e46983067b Adds support for activating and deactivating apps
iOS 13.3.1 limits free developer accounts to 3 apps and app extensions. As a workaround, we now allow up to 3 “active” apps (apps with installed provisioning profiles), as well as additional “inactivate” apps which don’t have any profiles installed, causing them to not count towards the total. Inactive apps cannot be opened until they are activated.
2020-03-11 15:49:26 -07:00
Riley Testut
d90637a59b [AltServer] Manages active/inactive profiles when installing apps 2020-03-11 13:51:39 -07:00
Riley Testut
90872dd03e [Both] Improves error messages 2020-03-11 13:51:17 -07:00
Riley Testut
c32ed758b4 Refreshes apps by installing provisioning profiles when possible
Assuming the certificate used to originally sign an app is still valid, we can refresh an app simply by installing new provisioning profiles. However, if the signing certificate is no longer valid, we fall back to the old method of resigning + reinstalling.
2020-03-06 17:34:18 -08:00
Riley Testut
61dabef55c [AltServer] Supports Install/Remove provisioning profiles requests
Stuff I shoulda committed
2020-03-06 17:14:29 -08:00
Riley Testut
b62cede294 Changes adjusted app group identifier format 2020-02-26 13:18:56 -08:00
Riley Testut
8ba6c320df [AltServer] Updates app version to 1.2.1 2020-02-26 13:16:15 -08:00
Riley Testut
e302eb34ef [AltServer] Fixes plug-in installation error when plug-ins directory does not exist 2020-02-14 17:02:15 -08:00
Riley Testut
ab8666d3aa [AltServer] Refactors Mail plug-in installation to fix notarization errors
AltServer must now download the Mail plug-in at runtime, because notarization will fail if AltServer contains an unsigned binary (and as of Catalina, Mail plug-ins only work if they’re unsigned)
2020-02-13 21:49:46 -08:00
Riley Testut
6847550702 Merge pull request #100 from rileytestut/develop
AltStore 1.2
2020-02-12 12:03:54 -08:00
Riley Testut
c77d9aa5c2 Merge branch 'master' of https://github.com/rileytestut/AltStore 2020-02-12 12:01:23 -08:00
Riley Testut
5ec0e0999e Updates apps.json with AltStore 1.2 2020-02-12 12:01:03 -08:00
Riley Testut
560ce8b709 [AltServer] Updates app version to 1.2 2020-02-12 12:00:50 -08:00
Riley Testut
3c746d6cb2 Limits fetching App IDs to debug builds 2020-02-12 08:01:54 -08:00
Riley Testut
efb84f7c04 Updates app version to 1.2 2020-02-12 00:12:08 -08:00
Riley Testut
081f45eac0 Updates apps.json 2020-02-11 19:01:48 -08:00
Riley Testut
b535929133 Updates app version to 1.2b4 2020-02-11 19:01:25 -08:00
Riley Testut
02dc9a1c62 Migrates from Core Data model v3 to v4 2020-02-11 18:40:18 -08:00
Riley Testut
c756cbdba4 Adds support for sideloading unc0ver 2020-02-11 13:29:28 -08:00
Riley Testut
f6e536a805 Improves App ID counting + management
Fetches App ID count directly from Apple, and adds AppIDsViewController to view all App IDs for the logged-in account.
2020-02-10 17:30:11 -08:00
Riley Testut
733f53109d Improves error message when registering app + app extension after App ID limit is reached 2020-02-10 16:30:54 -08:00
Riley Testut
7009a57fd7 Updates apps.json 2020-01-30 01:32:14 -08:00
Riley Testut
f7bfe0638a Replaces frameworks with static libraries
As of iOS 13.3.1, apps installed with free developer accounts that contain embedded frameworks fail to launch. To work around this, we now link all dependencies via Cocoapods as static libraries.
2020-01-29 23:21:08 -08:00
Riley Testut
9664d4fe7f [Both] Updates app version to 1.2b2 2020-01-27 12:55:12 -08:00
Riley Testut
c26b77f73f Updates apps.json 2020-01-27 12:53:44 -08:00
Riley Testut
5e59489287 Migrates from Core Data model v2 to v3 2020-01-24 16:11:42 -08:00
Riley Testut
281a3a9361 [AltServer] Disables wired connection timeout 2020-01-24 15:16:48 -08:00
Riley Testut
94f44da097 Removes “Delete App” functionality for non-debug builds
No longer necessary now that AltStore can detect when apps are uninstalled, but still useful for development.
2020-01-24 15:15:19 -08:00
Riley Testut
477f36cde6 Updates most InstalledApp/Extension properties when refreshing apps 2020-01-24 15:03:16 -08:00
Riley Testut
dc587510fc Displays remaining App ID count 2020-01-24 14:54:52 -08:00
Riley Testut
0c0c1c7a37 Improves 10 App ID limit error handling 2020-01-24 14:14:08 -08:00
Riley Testut
c3ff121441 Adjusts AltStore version font size 2020-01-24 11:34:26 -08:00
Riley Testut
d4e45214a5 Disables “Revoked AltStore Certificate” check for debug builds 2020-01-21 17:14:16 -08:00
Riley Testut
77715c8085 Stops wired connection listening socket when entering background 2020-01-21 17:11:16 -08:00
Riley Testut
1b2bb5ca46 Adds InstalledExtension 2020-01-21 16:53:34 -08:00
Riley Testut
a2de6929b5 Adds InstalledApp.installedDate 2020-01-21 16:49:38 -08:00
Riley Testut
91bf489058 Fixes “Device Already Registered” error 2020-01-21 15:12:48 -08:00
Riley Testut
c463a4bf6b [AltServer] Enables installing AltStore to devices over WiFi 2020-01-16 16:03:46 -08:00
Riley Testut
9ac44f8d9e [AltServer] Fixes session expiring when downloading apps on slow connection 2020-01-16 16:00:35 -08:00
Riley Testut
41cbf1bdd5 Changes resigned bundleID format to fix Keychain issues
Some apps (such as Cercube) can only access the Keychain if the app’s resigned bundle identifier is prefixed with the original bundle identifier.
2020-01-14 18:57:32 -08:00
Riley Testut
f04e0de990 Adds InstalledApp.team relationship 2020-01-14 18:39:44 -08:00
Riley Testut
88528ad35a Only updates ALTAppID features when they have changed 2020-01-14 13:20:26 -08:00
Riley Testut
c081560522 [AltServer] Fixes memory leaks when installing apps 2020-01-14 12:19:38 -08:00
Riley Testut
474565486e Fixes Patreon deep link not working on app launch 2020-01-13 13:53:04 -08:00
Riley Testut
d69b399c9d Displays version number in Settings 2020-01-13 13:32:55 -08:00
Riley Testut
38f666c0de Updates apps.json 2020-01-13 12:07:33 -08:00
Riley Testut
0e25aff1d4 Prevents deleting legacy sideloaded apps 2020-01-13 11:22:40 -08:00
Riley Testut
1e25955cd3 [Both] Updates app versions to 1.2b 2020-01-13 10:37:49 -08:00
Riley Testut
cbf6a3ad9c [AltServer] Updates bundle version to 6 2020-01-13 10:19:42 -08:00
Riley Testut
a980d65c59 Shares AltKit scheme 2020-01-13 10:17:38 -08:00
Riley Testut
64a5527049 Falls back to using canOpenURL to detect AltStore/Delta/Clip installs 2020-01-13 10:17:38 -08:00
Riley Testut
3fb190841e [Both] Adds support for installing apps over USB 2020-01-13 10:17:30 -08:00
Riley Testut
a4fab0367e Update main.yml 2020-01-13 10:14:05 -08:00
Riley Testut
f9edf97ffa Adds GitHub Action to post to Discord 2020-01-08 13:05:22 -08:00
Riley Testut
11255bbd14 Fixes session expiring when downloading apps on slow connection 2020-01-08 12:41:02 -08:00
Riley Testut
4be4a9895d Uses UTIs to determine whether apps are installed or not
AltStore now inserts an app-specific UTI when resigning apps, and it periodically checks whether that app has been deleted by checking whether UTTypeCopyDeclaration returns nil for the same app-specific UTI.
2019-12-17 19:17:45 -08:00
Riley Testut
a0b63d1eb4 Updates app version to 1.1.2 2019-12-16 13:56:23 -08:00
Riley Testut
80255a830e Updates apps.json for AltStore 1.1.2 2019-12-16 13:52:24 -08:00
Riley Testut
c4d2eaa17a Adds Clip to apps.json 2019-12-16 13:52:14 -08:00
Riley Testut
f12d469a52 Fixes crash when signing in
ALTAnisetteData.timeZone was nil for some users after receiving it from AltServer, so there is now a default time zone value to ensure it’s never nil.
2019-12-16 12:27:09 -08:00
Riley Testut
fbc58386c9 Updates apps.json for AltStore 1.1.1 and Delta 1.1.1 2019-12-13 11:58:53 -08:00
Riley Testut
08525926d1 [AltServer] Fixes erroneous “3 App Limit Reached” error 2019-12-11 13:05:12 -08:00
Riley Testut
15866f2cb3 Fixes increasing app size when refreshing
We now delete temporary directory + resigned .ipa before installation is complete to ensure AltStore doesn’t quit before we have the chance to.
2019-12-11 12:26:48 -08:00
Riley Testut
99ac8423cb Updates version to 1.1.1 2019-12-11 11:22:33 -08:00
Riley Testut
cb0a4b1c4c [AltServer] Updates version to 1.1.2 2019-12-11 11:22:31 -08:00
Riley Testut
c956e56b1d Updates AltSign 2019-12-11 10:54:32 -08:00
Riley Testut
963bd2cc71 Updates Patreon access code 2019-12-10 11:03:05 -08:00
Riley Testut
18d456e368 Removes app-specific password message on sign-in screen 2019-12-09 14:25:08 -08:00
Riley Testut
c8e2b1b5b5 [AltServer] Updates app version to 1.1.1 2019-11-28 12:20:17 -06:00
Riley Testut
9e3cae7622 Updates AltSign 2019-11-28 12:19:54 -06:00
Riley Testut
9adc96ad41 [AltServer] Replaces Mail’s bundleID in anisette data with Xcode’s 2019-11-28 12:13:28 -06:00
Riley Testut
7b5e6b8152 Updates apps.json 2019-11-19 01:40:55 -08:00
Riley Testut
39d34d558e [AltServer] Fixes notarization errors
- Compresses AltPlugin.mailbundle into .zip to prevent it from being signed when exporting archive
- Signs Sparkle framework with run script
2019-11-19 01:40:43 -08:00
Riley Testut
606de882a3 [AltServer] Adds Sparkle support 2019-11-18 15:42:10 -08:00
Riley Testut
3e5db1b26d [AltStore] Uses GrandSlam Authentication
Retrieves anisette data from AltServer so we can authenticate with GSA.
2019-11-18 14:49:17 -08:00
Riley Testut
10ff996ec9 [AltServer] Installs/uninstalls Mail.app plug-in 2019-11-18 14:42:38 -08:00
Riley Testut
ec7ab207c9 [AltServer] Uses GrandSlam Authentication
Uses Mail.app plug-in to retrieve the computer’s anisette data, which is necessary for GSA.
2019-11-18 14:17:57 -08:00
Riley Testut
3cca3e9a1b Updates apps.json for AltStore 1.1b2 2019-11-13 11:36:21 -08:00
Riley Testut
f016344eaa [AltServer] Adds STAGING flag to conditionally download Delta version 2019-11-13 11:35:37 -08:00
Riley Testut
a8f6bf0edb Updates version to 1.1b2 2019-11-05 18:42:16 -08:00
Riley Testut
9012243c55 Updates default ALTDeviceID 2019-11-05 18:09:35 -08:00
Riley Testut
75f30fe533 Fixes incorrect UpdateCollectionViewCell dimmed tint color 2019-11-05 18:08:58 -08:00
Riley Testut
c85228a70e Fixes AppViewController navigation bar appearing upon app becoming active 2019-11-05 18:08:11 -08:00
Riley Testut
3baf3e2de0 Fixes non-functional AppViewController download button 2019-11-05 18:06:52 -08:00
Riley Testut
9c37e143e4 [AltServer] Fixes dropping connection before client receives response 2019-11-05 18:05:32 -08:00
Riley Testut
8b1f30cafc Changes ALTTeamType.individual localizedDescription to “Developer” 2019-11-05 14:25:59 -08:00
Riley Testut
d8767ea4c5 [AltServer] Removes all free provisioning profiles when installing apps 2019-11-05 14:20:15 -08:00
Riley Testut
57f376cc65 Fixes endless loading when sideloading invalid app 2019-11-05 13:26:01 -08:00
Riley Testut
0bc07937c5 Updates launch screen 2019-11-05 13:24:26 -08:00
Riley Testut
bb2452ca9e [AltServer] Fixes notifications not appearing on Catalina 2019-11-04 15:08:20 -08:00
Riley Testut
590bafda03 Improves loading time when fetching patron names 2019-11-04 13:46:06 -08:00
Riley Testut
8df834a51a Fetches Patreon creator access token from AltStore source 2019-11-04 13:42:19 -08:00
Riley Testut
b502f8ed6e Adds STAGING flag to conditionally use staging endpoint 2019-11-04 13:38:54 -08:00
Riley Testut
10c359a29f Fixes tint colors not dimming when presenting alerts 2019-11-04 12:36:29 -08:00
Riley Testut
3c9bd51661 Fixes hard-to-see sideloading activity indicator in dark mode 2019-11-04 11:17:28 -08:00
Riley Testut
ad3756b8fd Updates apps.json for AltStore 1.1b 2019-10-28 14:24:59 -07:00
Riley Testut
f9b3a28e4e Revert "Uses ephemeral session when signing in to Patreon"
This reverts commit 9c2985825e.

Reverted because users might not be able to complete the login flow after verifying their emails.
2019-10-28 13:40:49 -07:00
Riley Testut
35d0ba85f6 Updates AltStore + AltServer to 1.1 2019-10-28 13:24:49 -07:00
Riley Testut
7c3bd9377f Fixes crash when installing unsigned apps 2019-10-28 13:23:36 -07:00
Riley Testut
1718b46220 Fixes issue where AltStore revokes its own certificate
Uses embedded certificate from AltServer if possible, but then falls back to asking user to refresh AltStore manually if the certificate used to install AltStore is revoked.
2019-10-28 13:16:55 -07:00
Riley Testut
26b8d6ad35 [AltServer] Embeds encrypted certificate in AltStore app bundle 2019-10-28 12:53:56 -07:00
Riley Testut
4c6291aa17 Merge branch 'feature/dark_mode' into develop 2019-10-28 12:17:48 -07:00
Riley Testut
9bbab08445 Fixes incorrect “No Updates” cell dark mode appearance 2019-10-28 12:17:07 -07:00
Riley Testut
ee68191c91 Adds support for dark mode 2019-10-24 13:04:30 -07:00
Riley Testut
aa88d488da Updates PillButton appearance 2019-10-23 14:20:01 -07:00
Riley Testut
a6c6c881fc Revises News tab + AppViewController UI 2019-10-23 14:19:32 -07:00
Riley Testut
a9f6556057 Revises My Apps UI 2019-10-23 14:07:13 -07:00
Riley Testut
ba9445b9e2 Revises Browse UI 2019-10-22 21:36:15 -07:00
Riley Testut
241f2d5998 Create FUNDING.yml 2019-10-18 02:00:32 -07:00
Riley Testut
9c2985825e Uses ephemeral session when signing in to Patreon 2019-10-17 14:52:42 -07:00
Riley Testut
daaeb0c6d9 Fixes Patreon login screen not appearing on iOS 13 2019-10-17 14:52:13 -07:00
Riley Testut
b8c83f44b2 Fixes prematurely cancelling authentication during interactive dismissal 2019-10-17 14:37:45 -07:00
Riley Testut
c103d27fd7 Updates apps.json for Delta 1.1 2019-10-17 13:13:49 -07:00
Riley Testut
64c6bf5471 Merge pull request #47 from coliff/patch-1
Fix typo
2019-10-14 10:07:27 -07:00
Christian Oliff
4c57ab590d Fix typo 2019-10-12 14:50:43 +09:00
Riley Testut
d3862cdfe1 Adds “Prevent AltStore Expiring” news item 2019-10-11 15:03:26 -07:00
Riley Testut
5049a2b07f Merge pull request #43 from ccheever/master
Add missing step to build instructions for AltServer in README
2019-10-09 16:51:28 -07:00
Charlie Cheever
7e3853d4e7 Add missing step to build instructions for AltServer in README
You need to run `carthage update` to build AltServer.
Adding that information to the README.
2019-10-08 01:28:16 -07:00
Riley Testut
1d9207f889 [AltServer] Updates bundle version to 2 2019-10-03 15:29:08 -07:00
Riley Testut
15d7d5c100 Updates apps.json 2019-10-03 15:28:53 -07:00
Riley Testut
86f02f94c9 Merge branch 'master' of https://github.com/rileytestut/AltStore 2019-10-03 15:27:45 -07:00
Riley Testut
25ff5b566f Opts-out of dark mode (for now) 2019-10-03 15:27:38 -07:00
Riley Testut
77c9dbdd7a Add LICENSE 2019-10-03 15:17:50 -07:00
Riley Testut
c4c4f8cff7 Adds README 2019-10-03 14:53:37 -07:00
Riley Testut
878dc35c83 Fixes incorrect permissions popover size on iOS 13 2019-10-03 13:52:47 -07:00
Riley Testut
cb3489f69c Fixes incorrect AppViewController header view size on iOS 13 2019-10-03 13:32:06 -07:00
Riley Testut
f1d287294d Handles iOS 13 dismiss gesture when signing in 2019-10-03 13:17:46 -07:00
Riley Testut
d76543d045 Fixes incorrect modal presentation of TabBarController on iOS 13 2019-10-03 12:36:49 -07:00
Riley Testut
7342f6d4b4 Fixes crash on launch on iOS 13 2019-10-03 12:30:53 -07:00
Riley Testut
198e7c7caf Fixes incorrect error message for expired Patreon access tokens 2019-10-03 12:27:12 -07:00
Riley Testut
1d740500f7 Updates AltSign 2019-09-30 13:59:17 -07:00
Riley Testut
fb054c440b [AltKit] Sets macOS deployment target to 10.14 2019-09-30 13:58:50 -07:00
Riley Testut
8c7f554909 Updates AltStore + AltServer to 1.0.1 2019-09-28 03:12:38 -07:00
Riley Testut
2b0e629dd1 Removes apps.json from bundled resources 2019-09-28 03:11:57 -07:00
Riley Testut
7a1f402c5d Fixes Login screen on iPhone SE 2019-09-27 18:56:18 -07:00
Riley Testut
ab56ce6004 Updates Patreon creator access token 2019-09-27 18:49:38 -07:00
Riley Testut
53e948c0a9 Improves error thrown when Patreon creator access token expires 2019-09-27 18:49:31 -07:00
Riley Testut
b4f8ae00db Updates release date for Delta, Delta (beta), and Clip 2019-09-27 17:40:40 -07:00
Riley Testut
9e610ddb73 Adds support for sideloading .ipa’s via “Open in…” 2019-09-27 17:39:36 -07:00
Riley Testut
7fc822948c [AltServer] Displays warning about revoking certificates when using developer Apple ID 2019-09-27 14:29:23 -07:00
597 changed files with 61052 additions and 21292 deletions

39
.editorconfig Normal file
View File

@@ -0,0 +1,39 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*.{js,py}]
charset = utf-8# 4 space indentation
# Swift files
[*.swift]
indent_style = space
indent_size = 4
charset = utf-8# 4 space indentation
# 4 space indentation
[*.py]
indent_style = space
indent_size = 4
# Tab indentation (no size specified)
[Makefile]
indent_style = tab
# Indentation override for all JS under lib directory
[lib/**.js]
indent_style = space
indent_size = 2
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
* @JoeMatt @lonkelle @nythepegasus @Spidy123222 @SternXD

40
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Bug Report
description: Report a bug
title: "[BUG] "
labels: ["bug"]
assignees: []
body:
- type: markdown
attributes:
value: |
## Please note that the issue tracker is not for support
Thanks for taking the time to fill out this bug report! Before you continue filling out the report, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the bug you are experiencing** in case it has already been reported.
**Please use [Discord](https://discord.gg/sidestore-949183273383395328) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
- type: textarea
id: description
attributes:
label: Describe the bug
description: What is the bug and how did you discover it?
placeholder: Please be clear and concise with your description.
validations:
required: true
- type: textarea
id: how-to-reproduce
attributes:
label: Instructions to reproduce
description: Please include clear and consistent instructions for reproducing the bug to make it easier for us to fix it.
validations:
required: true
- type: input
id: app-version
attributes:
label: What version of SideStore are you using?
description: To retrieve this, go to `Settings` in the SideStore app and scroll down to the bottom.
validations:
required: true
- type: textarea
id: other-info
attributes:
label: Other info
description: If you have any other comments, other info that might be useful, or if you found a workaround, please put it here.

10
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
# force issue template usage
blank_issues_enabled: false
contact_links:
- name: Discord
url: https://discord.gg/sidestore-949183273383395328
about: If you need support, please go here first instead of making an issue!
- name: GitHub Discussions
url: https://github.com/SideStore/SideStore/discussions
about: As an alternative to Discord, you can also make a new GitHub discussion.

View File

@@ -0,0 +1,32 @@
name: Feature Request
description: Suggest a feature
title: "[FEATURE REQUEST] "
labels: ["enhancement"]
assignees: []
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request! Before you continue filling out the form, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the feature you are suggestion** in case it has already been suggested.
**Please use [Discord](https://discord.gg/sidestore-949183273383395328) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
- type: textarea
id: description
attributes:
label: Describe the feature
description: What is the feature? How would it work?
placeholder: Please be clear and concise with your description.
validations:
required: true
- type: textarea
id: use-cases
attributes:
label: Use cases
description: Please include multiple use cases where this feature would be useful.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives
description: If you have alternative ideas of how this feature could work, you can put them here.

12
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,12 @@
### Changes
<!-- Fill this list with what your PR changes. Example: -->
- Fix bug
- Change UI for QOL
<!-- If your PR is ready to be merged, you can remove this section. -->
### Todo before merge
<!-- Example: -->
- [x] Finish UI changes
- [ ] Test

220
.github/workflows/alpha.yml vendored Normal file
View File

@@ -0,0 +1,220 @@
name: Alpha SideStore Build
on:
push:
branches: [staging]
workflow_dispatch:
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: macos-26
env:
DEPLOY_KEY: ${{ secrets.CROSS_REPO_PUSH_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_NAME: Alpha
CHANNEL: alpha
UPSTREAM_CHANNEL: "nightly"
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Find Last Successful commit
run: |
LAST_SUCCESSFUL_COMMIT=$(python3 scripts/ci/workflow.py last-successful-commit \
"false" "${{ env.CHANNEL }}" || echo "")
echo "LAST_SUCCESSFUL_COMMIT=$LAST_SUCCESSFUL_COMMIT" | tee -a $GITHUB_ENV
- run: brew install ldid xcbeautify
# --------------------------------------------------
# runtime env setup
# --------------------------------------------------
- name: Setup Env
run: |
BUILD_NUM="${{ github.run_number }}"
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
SHORT_COMMIT=$(python3 scripts/ci/workflow.py commit-id)
NORMALIZED_VERSION=$(python3 scripts/ci/workflow.py compute-normalized \
"$MARKETING_VERSION" \
"$BUILD_NUM" \
"$SHORT_COMMIT")
python3 scripts/ci/workflow.py set-marketing-version "$NORMALIZED_VERSION"
echo "BUILD_NUM=$BUILD_NUM" | tee -a $GITHUB_ENV
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: "26.2"
- name: Restore Cache (exact)
id: xcode-cache-exact
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: Restore Cache (last)
if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-
# --------------------------------------------------
# build and test
# --------------------------------------------------
- name: Clean
if: contains(github.event.head_commit.message, '[--clean-build]')
run: |
python3 scripts/ci/workflow.py clean
python3 scripts/ci/workflow.py clean-derived-data
python3 scripts/ci/workflow.py clean-spm-cache
- name: Boot simulator (async)
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
run: |
mkdir -p build/logs
python3 scripts/ci/workflow.py boot-sim-async "iPhone 17 Pro"
- name: Build
id: build
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-build
echo "encrypted=true" >> $GITHUB_OUTPUT
exit $STATUS
- name: Tests Build
id: test-build
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_BUILD == '1'
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py tests-build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-tests-build
exit $STATUS
- name: Save Cache
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: Tests Run
id: test-run
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py tests-run "iPhone 17 Pro"; STATUS=$?
python3 scripts/ci/workflow.py encrypt-tests-run
exit $STATUS
# --------------------------------------------------
# artifacts
# --------------------------------------------------
- uses: actions/upload-artifact@v4
with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip
- uses: actions/upload-artifact@v4
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_BUILD == '1'
with:
name: tests-build-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-build-logs.zip
- uses: actions/upload-artifact@v4
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
with:
name: tests-run-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-run-logs.zip
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore.ipa
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip
- uses: actions/checkout@v4
if: env.DEPLOY_KEY != ''
with:
repository: "SideStore/apps-v2.json"
ref: "main"
token: ${{ secrets.CROSS_REPO_PUSH_KEY }}
path: "SideStore/apps-v2.json"
- name: Generate Metadata
run: |
python3 scripts/ci/workflow.py dump-project-settings
PRODUCT_NAME=$(python3 scripts/ci/workflow.py read-product-name)
BUNDLE_ID=$(python3 scripts/ci/workflow.py read-bundle-id)
IPA_NAME="$PRODUCT_NAME.ipa"
python3 scripts/ci/workflow.py generate-metadata \
"$CHANNEL" \
"$SHORT_COMMIT" \
"$MARKETING_VERSION" \
"$CHANNEL" \
"$BUNDLE_ID" \
"$IPA_NAME" \
"$LAST_SUCCESSFUL_COMMIT"
- name: Deploy
if: env.DEPLOY_KEY != ''
run: |
SOURCE_JSON="_includes/source.json"
python3 scripts/ci/workflow.py deploy \
SideStore/apps-v2.json \
"$SOURCE_JSON" \
"$CHANNEL" \
"$MARKETING_VERSION"
# --------------------------------------------------
# upload release to GH
# --------------------------------------------------
- name: Upload Release
run: |
python3 scripts/ci/workflow.py upload-release \
"$RELEASE_NAME" \
"$CHANNEL" \
"$GITHUB_SHA" \
"$GITHUB_REPOSITORY" \
"$UPSTREAM_CHANNEL"

View File

@@ -0,0 +1,77 @@
name: Add artifact links to pull request and related issues
on:
workflow_run:
workflows: [Pull Request SideStore build]
types: [completed]
jobs:
artifacts-url-comments:
name: add artifact links to pull request and related issues job
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: add artifact links to pull request and related issues step
uses: tonyhallett/artifacts-url-comments@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
prefix: Builds for this Pull Request are available at
suffix: Have a nice day.
format: name
addTo: pull
# addTo: pullandissues
nightly-link-comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6
with:
# This snippet is public-domain, taken from
# https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml
script: |
async function upsertComment(owner, repo, issue_number, purpose, body) {
const {data: comments} = await github.rest.issues.listComments(
{owner, repo, issue_number});
const marker = `<!-- bot: ${purpose} -->`;
body = marker + "\n" + body;
const existing = comments.filter((c) => c.body.includes(marker));
if (existing.length > 0) {
const last = existing[existing.length - 1];
core.info(`Updating comment ${last.id}`);
await github.rest.issues.updateComment({
owner, repo,
body,
comment_id: last.id,
});
} else {
core.info(`Creating a comment in issue / PR #${issue_number}`);
await github.rest.issues.createComment({issue_number, body, owner, repo});
}
}
const {owner, repo} = context.repo;
const run_id = ${{github.event.workflow_run.id}};
const pull_requests = ${{ toJSON(github.event.workflow_run.pull_requests) }};
if (!pull_requests.length) {
return core.error("This workflow doesn't match any pull requests!");
}
const artifacts = await github.paginate(
github.rest.actions.listWorkflowRunArtifacts, {owner, repo, run_id});
if (!artifacts.length) {
return core.error(`No artifacts found`);
}
let body = `Download the artifacts for this pull request (nightly.link):\n`;
for (const art of artifacts) {
body += `\n* [${art.name}.zip](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}
core.info("Review thread message body:", body);
for (const pr of pull_requests) {
await upsertComment(owner, repo, pr.number,
"nightly-link", body);
}

260
.github/workflows/nightly.yml vendored Normal file
View File

@@ -0,0 +1,260 @@
name: Nightly SideStore Build
on:
push:
branches: [develop]
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: macos-26
env:
DEPLOY_KEY: ${{ secrets.CROSS_REPO_PUSH_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_NAME: Nightly
CHANNEL: nightly
UPSTREAM_CHANNEL: ""
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Find Last Successful commit
run: |
LAST_SUCCESSFUL_COMMIT=$(python3 scripts/ci/workflow.py last-successful-commit \
"false" "${{ env.CHANNEL }}" || echo "")
echo "LAST_SUCCESSFUL_COMMIT=$LAST_SUCCESSFUL_COMMIT" | tee -a $GITHUB_ENV
- name: Check for new changes (on schedule)
id: check_changes
if: github.event_name == 'schedule'
run: |
NEW_COMMITS=$(python3 scripts/ci/workflow.py count-new-commits "$LAST_SUCCESSFUL_COMMIT")
SHOULD_BUILD=$([ "${NEW_COMMITS:-0}" -ge 1 ] && echo true || echo false)
echo "should_build=$SHOULD_BUILD" >> $GITHUB_OUTPUT
echo "NEW_COMMITS=$NEW_COMMITS" | tee -a $GITHUB_ENV
- name: Should Skip Building (on schedule)
id: build_gate
run: |
SHOULD_SKIP=$(
{ [ "${{ github.event_name }}" = "schedule" ] && \
[ "${{ steps.check_changes.outputs.should_build }}" != "true" ]; \
} && echo true || echo false
)
echo "should_skip=$SHOULD_SKIP" >> $GITHUB_OUTPUT
- run: brew install ldid xcbeautify
if: steps.build_gate.outputs.should_skip != 'true'
# --------------------------------------------------
# runtime env setup
# --------------------------------------------------
- name: Setup Env
if: steps.build_gate.outputs.should_skip != 'true'
run: |
BUILD_NUM="${{ github.run_number }}"
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
SHORT_COMMIT=$(python3 scripts/ci/workflow.py commit-id)
NORMALIZED_VERSION=$(python3 scripts/ci/workflow.py compute-normalized \
"$MARKETING_VERSION" \
"$BUILD_NUM" \
"$SHORT_COMMIT")
python3 scripts/ci/workflow.py set-marketing-version "$NORMALIZED_VERSION"
echo "BUILD_NUM=$BUILD_NUM" | tee -a $GITHUB_ENV
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV
- name: Setup Xcode
if: steps.build_gate.outputs.should_skip != 'true'
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: "26.4"
- name: Restore Cache (exact)
if: steps.build_gate.outputs.should_skip != 'true'
id: xcode-cache-exact
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: Restore Cache (last)
if: >
steps.build_gate.outputs.should_skip != 'true' &&
steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-
# --------------------------------------------------
# build and test
# --------------------------------------------------
- name: Clean
if: steps.build_gate.outputs.should_skip != 'true' && contains(github.event.head_commit.message, '[--clean-build]')
run: |
python3 scripts/ci/workflow.py clean
python3 scripts/ci/workflow.py clean-derived-data
python3 scripts/ci/workflow.py clean-spm-cache
- name: Boot simulator (async)
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
run: |
mkdir -p build/logs
python3 scripts/ci/workflow.py boot-sim-async "iPhone 17 Pro"
- name: Build
if: steps.build_gate.outputs.should_skip != 'true'
id: build
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-build
echo "encrypted=true" >> $GITHUB_OUTPUT
exit $STATUS
- name: Tests Build
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_BUILD == '1'
id: test-build
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py tests-build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-tests-build
exit $STATUS
- name: Save Cache
if: >
steps.build_gate.outputs.should_skip != 'true' &&
steps.xcode-cache-fallback.outputs.cache-hit != 'true'
uses: actions/cache/save@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: Tests Run
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
id: test-run
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py tests-run "iPhone 17 Pro"; STATUS=$?
python3 scripts/ci/workflow.py encrypt-tests-run
exit $STATUS
# --------------------------------------------------
# artifacts
# --------------------------------------------------
- uses: actions/upload-artifact@v4
if: steps.build_gate.outputs.should_skip != 'true'
with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip
- uses: actions/upload-artifact@v4
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_BUILD == '1'
with:
name: tests-build-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-build-logs.zip
- uses: actions/upload-artifact@v4
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
with:
name: tests-run-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-run-logs.zip
- uses: actions/upload-artifact@v4
if: steps.build_gate.outputs.should_skip != 'true'
with:
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore.ipa
- uses: actions/upload-artifact@v4
if: steps.build_gate.outputs.should_skip != 'true'
with:
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip
- uses: actions/checkout@v4
if: steps.build_gate.outputs.should_skip != 'true' && env.DEPLOY_KEY != ''
with:
repository: "SideStore/apps-v2.json"
ref: "main"
token: ${{ secrets.CROSS_REPO_PUSH_KEY }}
path: "SideStore/apps-v2.json"
- name: Generate Metadata
if: steps.build_gate.outputs.should_skip != 'true'
run: |
python3 scripts/ci/workflow.py dump-project-settings
PRODUCT_NAME=$(python3 scripts/ci/workflow.py read-product-name)
BUNDLE_ID=$(python3 scripts/ci/workflow.py read-bundle-id)
IPA_NAME="$PRODUCT_NAME.ipa"
python3 scripts/ci/workflow.py generate-metadata \
"$CHANNEL" \
"$SHORT_COMMIT" \
"$MARKETING_VERSION" \
"$CHANNEL" \
"$BUNDLE_ID" \
"$IPA_NAME" \
"$LAST_SUCCESSFUL_COMMIT"
- name: Deploy
if: steps.build_gate.outputs.should_skip != 'true' && env.DEPLOY_KEY != ''
run: |
SOURCE_JSON="_includes/source.json"
python3 scripts/ci/workflow.py deploy \
SideStore/apps-v2.json \
"$SOURCE_JSON" \
"$CHANNEL" \
"$MARKETING_VERSION"
# --------------------------------------------------
# upload release to GH
# --------------------------------------------------
- name: Upload Release
if: steps.build_gate.outputs.should_skip != 'true'
run: |
python3 scripts/ci/workflow.py upload-release \
"$RELEASE_NAME" \
"$CHANNEL" \
"$GITHUB_SHA" \
"$GITHUB_REPOSITORY" \
"$UPSTREAM_CHANNEL"

90
.github/workflows/pr.yml vendored Normal file
View File

@@ -0,0 +1,90 @@
name: Pull Request SideStore build
on:
pull_request:
# types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
types: [opened, synchronize, reopened, ready_for_review]
concurrency:
group: pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
build:
name: Build and upload SideStore
if: ${{ github.event.pull_request.draft == false }}
runs-on: macos-26
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 1 # shallow clone just for PR
- run: brew install ldid xcbeautify
- name: Setup Env
run: |
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
SHORT_COMMIT=$(git rev-parse --short ${{ github.event.pull_request.head.sha }})
NORMALIZED_VERSION="${MARKETING_VERSION}-pr.${{ github.event.pull_request.number }}+${SHORT_COMMIT}"
python3 scripts/ci/workflow.py set-marketing-version "$NORMALIZED_VERSION"
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: "26.2"
- name: Restore Cache (exact)
id: xcode-cache-exact
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: Restore Cache (last)
if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-
- name: Build
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-build
mv SideStore.ipa SideStore-${{ env.MARKETING_VERSION }}.ipa
exit $STATUS
- name: Save Cache
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- uses: actions/upload-artifact@v4
with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore-${{ env.MARKETING_VERSION }}.ipa
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip

135
.github/workflows/stable.yml vendored Normal file
View File

@@ -0,0 +1,135 @@
name: Stable SideStore build
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
workflow_dispatch:
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build SideStore - stable
runs-on: macos-26
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CHANNEL: stable
UPSTREAM_CHANNEL: ""
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Find Last Successful commit
run: |
LAST_SUCCESSFUL_COMMIT=$(python3 scripts/ci/workflow.py last-successful-commit \
"true" || echo "")
echo "LAST_SUCCESSFUL_COMMIT=$LAST_SUCCESSFUL_COMMIT" | tee -a $GITHUB_ENV
- run: brew install ldid xcbeautify
- name: Setup Env
run: |
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
SHORT_COMMIT=$(python3 scripts/ci/workflow.py commit-id)
if [ "$MARKETING_VERSION" != "${{ github.ref_name }}" ]; then
echo "Version mismatch"
echo "Build.xcconfig: $MARKETING_VERSION"
echo "Tag: ${{ github.ref_name }}"
exit 1
fi
echo "MARKETING_VERSION=$MARKETING_VERSION" | tee -a $GITHUB_ENV
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: "26.4"
- name: Restore Cache (exact)
id: xcode-cache-exact
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-stable-${{ github.sha }}
- name: Restore Cache (last)
if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-stable-
- name: Build
id: build
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-build
exit $STATUS
- name: Save Cache
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-stable-${{ github.sha }}
- uses: actions/upload-artifact@v4
with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore.ipa
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip
- name: Generate Metadata
run: |
python3 scripts/ci/workflow.py dump-project-settings
PRODUCT_NAME=$(python3 scripts/ci/workflow.py read-product-name)
BUNDLE_ID=$(python3 scripts/ci/workflow.py read-bundle-id)
IPA_NAME="$PRODUCT_NAME.ipa"
python3 scripts/ci/workflow.py generate-metadata \
"${{ github.ref_name }}" \
"$SHORT_COMMIT" \
"$MARKETING_VERSION" \
"$CHANNEL" \
"$BUNDLE_ID" \
"$IPA_NAME" \
"$LAST_SUCCESSFUL_COMMIT"
- name: Upload to releases
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python3 scripts/ci/workflow.py upload-release \
"${{ github.ref_name }}" \
"${{ github.ref_name }}" \
"$GITHUB_SHA" \
"$GITHUB_REPOSITORY" \
"$UPSTREAM_CHANNEL" \
"true"

49
.gitignore vendored
View File

@@ -1,14 +1,18 @@
# macOS
#
*.DS_Store
**/*.DS_Store
# Xcode
#
## CocoaPods
Pods/
## Build generated
build/
DerivedData
SideStore.xcarchive
## Various settings
*.pbxuser
!default.pbxuser
@@ -27,4 +31,45 @@ xcuserdata
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.hmap
/newrelic_agent.log
/CodeSigning.xcconfig
/.vscode
## AppCode specific
.idea/
Payload/
**/SideStore.ipa
**/AltBackup.ipa
**/*.dSYM
Dependencies/.*-prebuilt-fetch-*
SideStore/minimuxer/*
SideStore/em_proxy/*
!Dependencies/**/.gitkeep
.nightly-build-num
## em_proxy and minimuxer biaries
**/.last-prebuilt-fetch-em_proxy
**/.last-prebuilt-fetch-minimuxer
# misc
**/output.txt
SideStore/.skip-prebuilt-fetch-minimuxer
SideStore/.skip-prebuilt-fetch-em_proxy
.git.bkp/
# Never check-in this package.resolved file
# coz SPM then resolves packages using the stale entries in this file
*.xcodeproj/**/Package.resolved
*.xcworkspace/**/Package.resolved
# some more commandline build artifacts
test-recording.mp4
test-recording.log
altstore-sources.md
local-build.sh
source-metadata.json
release-notes.md

71
.gitmodules vendored
View File

@@ -1,15 +1,64 @@
#-------------------------------
# When changing url/branch in this .gitmodules file,
# Always ensure you run:
# 1. `git rm --cached <submodule_relative_path>` # this removes the submodule entry from general git tracking
# 2. `rm -rf .git/modules/<submodule_relative_path>` # this removes the stale name entries in submodule tracker
# 3. `rm -rf <submodule_relative_path>` # removes the submodule completely
# 4. `git submodule --deinit <submodule_relative_path>` # make sure that the submodule is de-inited too (ignore errors at this point)
# 5. `git submodule add [-b <branch_name>] <repo_url> <submodule_relative_path>` # This adds the submodule back into general git tracking and also adds to the submodule tracker
# 6. Step 5 creates an entry in the .gitmodules when a submodule is added,
# So if you already had one entry, try to remove duplicates at this point
# 7. `git submodule sync --recursive` # this now sets/updates the submodule repo url tracker into git config
# 8. `git submodule update --init --recursive` # this now clones the updated repo set by .gitmodules
# But this will always fetch the latest commit sepecified by the custom(if set)/default branch
# 9. If you do want to have a specific commit in that submodule branch and not latest, you need to perform normal detached head checkout and check-in as follows:
# `pushd <submodule_relative_path>` # switch to the submodule repo
# `git checkout <commit-id>` # this creates a detached head state
# `popd` # get back to parent repo
# `git add <submodule_relative_path>` # check-in the changes in parent for this submodule link (tracker)
# `git commit -m <commit-message>` # commit it to parent repo
# `git push` # push to parent repo to preserve this entire change in the submodule repo/link file
#
# NOTES:
# 1. updating just this .gitmodules file is NOT ENOUGH when changing repo url and performing a simple `git submodule update --init --recursive`, need to do all the above listed steps for proper tracking
# 2. updating the branch in this .gitmodules for same repo is okay as long as `git submodule update --init --recursive` is also performed followed by it
# 3. Ensure there is no stale entries or duplicate entries in this .gitmodules file coz, `git submodule add ...` creates an entry here.
#-------------------------------
[submodule "Dependencies/Roxas"]
path = Dependencies/Roxas
url = https://github.com/rileytestut/Roxas.git
[submodule "Dependencies/AltSign"]
path = Dependencies/AltSign
url = https://github.com/rileytestut/AltSign.git
path = Dependencies/Roxas
url = https://github.com/SideStore/Roxas.git
[submodule "Dependencies/libimobiledevice"]
path = Dependencies/libimobiledevice
url = https://github.com/rileytestut/libimobiledevice.git
path = Dependencies/libimobiledevice
url = https://github.com/SideStore/libimobiledevice
[submodule "Dependencies/libusbmuxd"]
path = Dependencies/libusbmuxd
url = https://github.com/libimobiledevice/libusbmuxd.git
path = Dependencies/libusbmuxd
url = https://github.com/libimobiledevice/libusbmuxd.git
[submodule "Dependencies/libplist"]
path = Dependencies/libplist
url = https://github.com/libimobiledevice/libplist.git
path = Dependencies/libplist
url = https://github.com/SideStore/libplist.git
[submodule "Dependencies/MarkdownAttributedString"]
path = Dependencies/MarkdownAttributedString
url = https://github.com/chockenberry/MarkdownAttributedString.git
[submodule "Dependencies/libimobiledevice-glue"]
path = Dependencies/libimobiledevice-glue
url = https://github.com/libimobiledevice/libimobiledevice-glue
#sidestore dependencies
[submodule "Dependencies/minimuxer"]
path = Dependencies/minimuxer
url = https://github.com/SideStore/minimuxer
branch = master
[submodule "Dependencies/em_proxy"]
path = Dependencies/em_proxy
url = https://github.com/SideStore/em_proxy
branch = master
[submodule "Dependencies/apps-v2.json"]
path = Dependencies/apps-v2.json
url = https://github.com/SideStore/apps-v2.json
branch = main
[submodule "Dependencies/AltSign"]
path = Dependencies/AltSign
url = https://github.com/SideStore/AltSign
branch = master

View File

@@ -2,7 +2,9 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.$(GROUP_ID)</string>
</array>
</dict>
</plist>

133
AltBackup/AppDelegate.swift Normal file
View File

@@ -0,0 +1,133 @@
//
// AppDelegate.swift
// AltBackup
//
// Created by Riley Testut on 5/11/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import UIKit
extension AppDelegate
{
static let startBackupNotification = Notification.Name("io.sidestore.StartBackup")
static let startRestoreNotification = Notification.Name("io.sidestore.StartRestore")
static let operationDidFinishNotification = Notification.Name("io.sidestore.BackupOperationFinished")
static let operationResultKey = "result"
}
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
private var currentBackupReturnURL: URL?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
// Override point for customization after application launch.
NotificationCenter.default.addObserver(self, selector: #selector(AppDelegate.operationDidFinish(_:)), name: AppDelegate.operationDidFinishNotification, object: nil)
let viewController = ViewController()
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = viewController
self.window?.makeKeyAndVisible()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
{
return self.open(url)
}
}
private extension AppDelegate
{
func open(_ url: URL) -> Bool
{
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false }
guard let command = components.host?.lowercased() else { return false }
switch command
{
case "backup":
guard let returnString = components.queryItems?.first(where: { $0.name == "returnURL" })?.value, let returnURL = URL(string: returnString) else { return false }
self.currentBackupReturnURL = returnURL
NotificationCenter.default.post(name: AppDelegate.startBackupNotification, object: nil)
return true
case "restore":
guard let returnString = components.queryItems?.first(where: { $0.name == "returnURL" })?.value, let returnURL = URL(string: returnString) else { return false }
self.currentBackupReturnURL = returnURL
NotificationCenter.default.post(name: AppDelegate.startRestoreNotification, object: nil)
return true
default: return false
}
}
@objc func operationDidFinish(_ notification: Notification)
{
defer {
self.currentBackupReturnURL = nil
}
// TODO: @mahee96: This doesn't account cases where backup is too long and user switched to other apps
// The check for self.currentBackupReturnURL when backup/restore was still in progress but app switched
// between FG/BG is improper, since it will ignore(eat up) the response(success/failure) to parent
//
// This leaves the backup/restore to show dummy animation forever
guard
let returnURL = self.currentBackupReturnURL,
let result = notification.userInfo?[AppDelegate.operationResultKey] as? Result<Void, Error>
else {
return // This is bad (Needs fixing - never eat up response like this unless there is no context to post response to!)
}
guard var components = URLComponents(url: returnURL, resolvingAgainstBaseURL: false) else {
return // This is ASSERTION Failure, ie RETURN URL needs to be valid. So ignoring (eating up) response is not the solution
}
switch result
{
case .success:
components.path = "/success"
case .failure(let error as NSError):
components.path = "/failure"
components.queryItems = ["errorDomain": error.domain,
"errorCode": String(error.code),
"errorDescription": error.localizedDescription].map { URLQueryItem(name: $0, value: $1) }
}
guard let responseURL = components.url else { return }
DispatchQueue.main.async {
// Response to the caller/parent app is posted here (url is provided by caller in incoming query params)
UIApplication.shared.open(responseURL, options: [:]) { (success) in
print("Sent response to app with success:", success)
}
}
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.518",
"green" : "0.502",
"red" : "0.004"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.404",
"green" : "0.322",
"red" : "0.008"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.750",
"blue" : "0xFF",
"green" : "0xFF",
"red" : "0xFF"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

342
AltBackup/BackupController.swift Executable file
View File

@@ -0,0 +1,342 @@
//
// BackupController.swift
// AltBackup
//
// Created by Riley Testut on 5/12/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
extension ErrorUserInfoKey
{
static let sourceFile: String = "alt_sourceFile"
static let sourceFileLine: String = "alt_sourceFileLine"
}
extension Error
{
var sourceDescription: String? {
guard let sourceFile = (self as NSError).userInfo[ErrorUserInfoKey.sourceFile] as? String, let sourceFileLine = (self as NSError).userInfo[ErrorUserInfoKey.sourceFileLine] else {
return nil
}
return "(\((sourceFile as NSString).lastPathComponent), Line \(sourceFileLine))"
}
}
struct BackupError: ALTLocalizedError
{
enum Code: ALTErrorEnum, RawRepresentable
{
case invalidBundleID
case appGroupNotFound(String?)
case randomError // Used for debugging.
// Provide failure reason for each error code
var errorFailureReason: String {
switch self {
case .invalidBundleID:
return NSLocalizedString("The bundle identifier is invalid.", comment: "")
case .appGroupNotFound(let appGroup):
if let appGroup = appGroup {
return String(format: NSLocalizedString("The app group “%@” could not be found.", comment: ""), appGroup)
} else {
return NSLocalizedString("The AltStore app group could not be found.", comment: "")
}
case .randomError:
return NSLocalizedString("A random error occurred.", comment: "")
}
}
static var errorDomain: String {
return "com.sidestore.BackupError"
}
// Add a raw value for RawRepresentable conformance
var rawValue: Int {
switch self {
case .invalidBundleID: return 0
case .appGroupNotFound: return 1
case .randomError: return 2
}
}
// Initializer for RawRepresentable
init?(rawValue: Int) {
switch rawValue {
case 0: self = .invalidBundleID
case 1: self = .appGroupNotFound(nil)
case 2: self = .randomError
default: return nil
}
}
}
let code: Code
let sourceFile: String
let sourceFileLine: Int
var failure: String?
var errorTitle: String?
var errorFailure: String?
var failureReason: String? {
switch self.code
{
case .invalidBundleID: return NSLocalizedString("The bundle identifier is invalid.", comment: "")
case .appGroupNotFound(let appGroup):
if let appGroup = appGroup
{
return String(format: NSLocalizedString("The app group “%@” could not be found.", comment: ""), appGroup)
}
else
{
return NSLocalizedString("The AltStore app group could not be found.", comment: "")
}
case .randomError: return NSLocalizedString("A random error occured.", comment: "")
}
}
var errorUserInfo: [String : Any] {
let userInfo: [String: Any?] = [NSLocalizedDescriptionKey: self.errorDescription,
NSLocalizedFailureReasonErrorKey: self.failureReason,
NSLocalizedFailureErrorKey: self.failure,
ErrorUserInfoKey.sourceFile: self.sourceFile,
ErrorUserInfoKey.sourceFileLine: self.sourceFileLine]
return userInfo.compactMapValues { $0 }
}
// Implement description for CustomStringConvertible
var description: String {
return "\(errorTitle ?? "Unknown Error"): \(failureReason ?? "No reason available")"
}
init(_ code: Code, description: String? = nil, file: String = #file, line: Int = #line)
{
self.code = code
self.failure = description
self.sourceFile = file
self.sourceFileLine = line
self.errorTitle = NSLocalizedString("Backup Error", comment: "")
self.errorFailure = description
}
}
class BackupController: NSObject
{
private let fileCoordinator = NSFileCoordinator(filePresenter: nil)
private let operationQueue = OperationQueue()
override init()
{
self.operationQueue.name = "AltBackup-BackupQueue"
}
func performBackup(completionHandler: @escaping (Result<Void, Error>) -> Void)
{
do
{
guard let bundleIdentifier = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.altBundleID) as? String else {
throw BackupError(.invalidBundleID, description: NSLocalizedString("Unable to create backup directory.", comment: ""))
}
guard
let altstoreAppGroup = Bundle.main.altstoreAppGroup,
let sharedDirectoryURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: altstoreAppGroup)
else {
throw BackupError(.appGroupNotFound(nil), description: NSLocalizedString("Unable to create backup directory.", comment: ""))
}
let backupsDirectory = sharedDirectoryURL.appendingPathComponent("Backups")
// Use temporary directory to prevent messing up successful backup with incomplete one.
let temporaryAppBackupDirectory = backupsDirectory.appendingPathComponent("Temp", isDirectory: true).appendingPathComponent(UUID().uuidString)
let appBackupDirectory = backupsDirectory.appendingPathComponent(bundleIdentifier)
let writingIntent = NSFileAccessIntent.writingIntent(with: temporaryAppBackupDirectory, options: [])
let replacementIntent = NSFileAccessIntent.writingIntent(with: appBackupDirectory, options: [.forReplacing])
self.fileCoordinator.coordinate(with: [writingIntent, replacementIntent], queue: self.operationQueue) { (error) in
do
{
if let error = error
{
throw error
}
do
{
let mainGroupBackupDirectory = temporaryAppBackupDirectory.appendingPathComponent("App")
try FileManager.default.createDirectory(at: mainGroupBackupDirectory, withIntermediateDirectories: true, attributes: nil)
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let backupDocumentsDirectory = mainGroupBackupDirectory.appendingPathComponent(documentsDirectory.lastPathComponent)
if FileManager.default.fileExists(atPath: backupDocumentsDirectory.path)
{
try FileManager.default.removeItem(at: backupDocumentsDirectory)
}
if FileManager.default.fileExists(atPath: documentsDirectory.path)
{
try FileManager.default.copyItem(at: documentsDirectory, to: backupDocumentsDirectory)
}
print("Copied Documents directory from \(documentsDirectory) to \(backupDocumentsDirectory)")
let libraryDirectory = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0]
let backupLibraryDirectory = mainGroupBackupDirectory.appendingPathComponent(libraryDirectory.lastPathComponent)
if FileManager.default.fileExists(atPath: backupLibraryDirectory.path)
{
try FileManager.default.removeItem(at: backupLibraryDirectory)
}
if FileManager.default.fileExists(atPath: libraryDirectory.path)
{
try FileManager.default.copyItem(at: libraryDirectory, to: backupLibraryDirectory)
}
print("Copied Library directory from \(libraryDirectory) to \(backupLibraryDirectory)")
}
for appGroup in Bundle.main.appGroups where appGroup != altstoreAppGroup
{
guard let appGroupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
throw BackupError(.appGroupNotFound(appGroup), description: NSLocalizedString("Unable to create app group backup directory.", comment: ""))
}
let backupAppGroupURL = temporaryAppBackupDirectory.appendingPathComponent(appGroup)
// There are several system hidden files that we don't have permission to read, so we just skip all hidden files in app group directories.
try self.copyDirectoryContents(at: appGroupURL, to: backupAppGroupURL, options: [.skipsHiddenFiles])
}
// Replace previous backup with new backup.
_ = try FileManager.default.replaceItemAt(appBackupDirectory, withItemAt: temporaryAppBackupDirectory)
print("Replaced previous backup with new backup:", temporaryAppBackupDirectory)
completionHandler(.success(()))
}
catch
{
do { try FileManager.default.removeItem(at: temporaryAppBackupDirectory) }
catch { print("Failed to remove temporary directory.", error) }
completionHandler(.failure(error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
func restoreBackup(completionHandler: @escaping (Result<Void, Error>) -> Void)
{
do
{
guard let bundleIdentifier = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.altBundleID) as? String else {
throw BackupError(.invalidBundleID, description: NSLocalizedString("Unable to access backup.", comment: ""))
}
guard
let altstoreAppGroup = Bundle.main.altstoreAppGroup,
let sharedDirectoryURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: altstoreAppGroup)
else { throw BackupError(.appGroupNotFound(nil), description: NSLocalizedString("Unable to access backup.", comment: "")) }
let backupsDirectory = sharedDirectoryURL.appendingPathComponent("Backups")
let appBackupDirectory = backupsDirectory.appendingPathComponent(bundleIdentifier)
let readingIntent = NSFileAccessIntent.readingIntent(with: appBackupDirectory, options: [])
self.fileCoordinator.coordinate(with: [readingIntent], queue: self.operationQueue) { (error) in
do
{
if let error = error
{
throw error
}
let mainGroupBackupDirectory = appBackupDirectory.appendingPathComponent("App")
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let backupDocumentsDirectory = mainGroupBackupDirectory.appendingPathComponent(documentsDirectory.lastPathComponent)
let libraryDirectory = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0]
let backupLibraryDirectory = mainGroupBackupDirectory.appendingPathComponent(libraryDirectory.lastPathComponent)
try self.copyDirectoryContents(at: backupDocumentsDirectory, to: documentsDirectory)
try self.copyDirectoryContents(at: backupLibraryDirectory, to: libraryDirectory)
for appGroup in Bundle.main.appGroups where appGroup != altstoreAppGroup
{
guard let appGroupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
throw BackupError(.appGroupNotFound(appGroup), description: NSLocalizedString("Unable to read app group backup.", comment: ""))
}
let backupAppGroupURL = appBackupDirectory.appendingPathComponent(appGroup)
try self.copyDirectoryContents(at: backupAppGroupURL, to: appGroupURL)
}
completionHandler(.success(()))
}
catch
{
completionHandler(.failure(error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
private extension BackupController
{
func copyDirectoryContents(at sourceDirectoryURL: URL, to destinationDirectoryURL: URL, options: FileManager.DirectoryEnumerationOptions = []) throws
{
guard FileManager.default.fileExists(atPath: sourceDirectoryURL.path) else { return }
if !FileManager.default.fileExists(atPath: destinationDirectoryURL.path)
{
try FileManager.default.createDirectory(at: destinationDirectoryURL, withIntermediateDirectories: true, attributes: nil)
}
for fileURL in try FileManager.default.contentsOfDirectory(at: sourceDirectoryURL, includingPropertiesForKeys: [.isDirectoryKey], options: options)
{
let isDirectory = try fileURL.resourceValues(forKeys: [.isDirectoryKey]).isDirectory ?? false
let destinationURL = destinationDirectoryURL.appendingPathComponent(fileURL.lastPathComponent)
if FileManager.default.fileExists(atPath: destinationURL.path)
{
do {
try FileManager.default.removeItem(at: destinationURL)
}
catch CocoaError.fileWriteNoPermission where isDirectory {
try self.copyDirectoryContents(at: fileURL, to: destinationURL, options: options)
continue
}
catch {
print(error)
throw error
}
}
do {
try FileManager.default.copyItem(at: fileURL, to: destinationURL)
print("Copied item from \(fileURL) to \(destinationURL)")
}
catch let error where fileURL.lastPathComponent == "Inbox" && fileURL.deletingLastPathComponent().lastPathComponent == "Documents" {
// Ignore errors for /Documents/Inbox
print("Failed to copy Inbox directory:", error)
}
catch {
print(error)
throw error
}
}
}
}

66
AltBackup/Info.plist Normal file
View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ALTAppGroups</key>
<array>
<string>group.$(APP_GROUP_IDENTIFIER)</string>
</array>
<key>ALTBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>SideBackup General</string>
<key>CFBundleURLSchemes</key>
<array>
<string>sidebackup</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportsDocumentBrowser</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" name="Background"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<namedColor name="Background">
<color red="0.0040000001899898052" green="0.50199997425079346" blue="0.51800000667572021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>application-identifier</key>
<string>XYZ0123456.com.SideStore.SideStore.AltBackup</string>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.team-identifier</key>
<string>XYZ0123456</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.SideStore.SideStore</string>
</array>
<key>get-task-allow</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,15 @@
//
// UIColor+AltBackup.swift
// AltBackup
//
// Created by Riley Testut on 5/11/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import UIKit
extension UIColor
{
static let altstoreBackground = UIColor(named: "Background")!
static let altstoreText = UIColor(named: "Text")!
}

View File

@@ -0,0 +1,212 @@
//
// ViewController.swift
// AltBackup
//
// Created by Riley Testut on 5/11/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import UIKit
extension Bundle
{
var appName: String? {
let appName =
Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ??
Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as? String
return appName
}
}
extension ViewController
{
enum BackupOperation
{
case backup
case restore
}
}
class ViewController: UIViewController
{
private let backupController = BackupController()
private var currentOperation: BackupOperation? {
didSet {
DispatchQueue.main.async {
self.update()
}
}
}
private var textLabel: UILabel!
private var detailTextLabel: UILabel!
private var activityIndicatorView: UIActivityIndicatorView!
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
{
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.backup), name: AppDelegate.startBackupNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.restore), name: AppDelegate.startRestoreNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.didEnterBackground(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
required init?(coder: NSCoder) {
fatalError()
}
override func viewDidLoad()
{
super.viewDidLoad()
self.view.backgroundColor = .altstoreBackground
self.textLabel = UILabel(frame: .zero)
self.textLabel.font = UIFont.preferredFont(forTextStyle: .title2)
self.textLabel.textColor = .altstoreText
self.textLabel.textAlignment = .center
self.textLabel.numberOfLines = 0
self.detailTextLabel = UILabel(frame: .zero)
self.detailTextLabel.font = UIFont.preferredFont(forTextStyle: .body)
self.detailTextLabel.textColor = .altstoreText
self.detailTextLabel.textAlignment = .center
self.detailTextLabel.numberOfLines = 0
self.activityIndicatorView = UIActivityIndicatorView(style: .whiteLarge)
self.activityIndicatorView.color = .altstoreText
self.activityIndicatorView.startAnimating()
// TODO: @mahee96: Disabled these backup/restore buttons in altbackup.app screen which were present for debugging purpose.
// Can find something useful for these later, but these are not required by this backup/restore app
// #if DEBUG
// let button1 = UIButton(type: .system)
// button1.setTitle("Backup", for: .normal)
// button1.setTitleColor(.white, for: .normal)
// button1.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
// button1.addTarget(self, action: #selector(ViewController.backup), for: .primaryActionTriggered)
//
// let button2 = UIButton(type: .system)
// button2.setTitle("Restore", for: .normal)
// button2.setTitleColor(.white, for: .normal)
// button2.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
// button2.addTarget(self, action: #selector(ViewController.restore), for: .primaryActionTriggered)
//
// let arrangedSubviews = [self.textLabel!, self.detailTextLabel!, self.activityIndicatorView!, button1, button2]
// #else
let arrangedSubviews = [self.textLabel!, self.detailTextLabel!, self.activityIndicatorView!]
// #endif
let stackView = UIStackView(arrangedSubviews: arrangedSubviews)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 22
stackView.axis = .vertical
stackView.alignment = .center
self.view.addSubview(stackView)
NSLayoutConstraint.activate([stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
stackView.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: self.view.safeAreaLayoutGuide.leadingAnchor, multiplier: 1.0),
self.view.safeAreaLayoutGuide.trailingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: stackView.trailingAnchor, multiplier: 1.0)])
self.update()
}
}
private extension ViewController
{
@objc func backup()
{
self.currentOperation = .backup
self.backupController.performBackup { (result) in
let appName = Bundle.main.appName ?? NSLocalizedString("App", comment: "")
let title = String(format: NSLocalizedString("%@ could not be backed up.", comment: ""), appName)
self.process(result, errorTitle: title)
}
}
@objc func restore()
{
self.currentOperation = .restore
self.backupController.restoreBackup { (result) in
let appName = Bundle.main.appName ?? NSLocalizedString("App", comment: "")
let title = String(format: NSLocalizedString("%@ could not be restored.", comment: ""), appName)
self.process(result, errorTitle: title)
}
}
func update()
{
switch self.currentOperation
{
case .backup:
self.textLabel.text = NSLocalizedString("Backing up app data…", comment: "")
self.detailTextLabel.isHidden = true
self.activityIndicatorView.startAnimating()
case .restore:
self.textLabel.text = NSLocalizedString("Restoring app data…", comment: "")
self.detailTextLabel.isHidden = true
self.activityIndicatorView.startAnimating()
// TODO: @mahee96: This is pointless since, app going in bg/fg should still report its last operation properly
case .none:
self.textLabel.text = String(format: NSLocalizedString("%@ is inactive.", comment: ""),
Bundle.main.appName ?? NSLocalizedString("App", comment: ""))
self.detailTextLabel.text = String(format: NSLocalizedString("Refresh %@ in SideStore to continue using it.", comment: ""),
Bundle.main.appName ?? NSLocalizedString("this app", comment: ""))
self.detailTextLabel.isHidden = false
self.activityIndicatorView.stopAnimating()
}
}
}
private extension ViewController
{
func process(_ result: Result<Void, Error>, errorTitle: String)
{
DispatchQueue.main.async {
switch result
{
case .success: break
case .failure(let error as NSError):
let message: String
if let sourceDescription = error.sourceDescription
{
message = error.localizedDescription + "\n\n" + sourceDescription
}
else
{
message = error.localizedDescription
}
let alertController = UIAlertController(title: errorTitle, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
NotificationCenter.default.post(name: AppDelegate.operationDidFinishNotification, object: nil, userInfo: [AppDelegate.operationResultKey: result])
}
}
// TODO: @mahee96: This doesn't account cases where backup is too long and user switched to other apps
// Now the user has lost his progress since current operation was cancelled due to switch between FG and BG
// if this just the reset for enum such that UI stops showing progress circle, then this is fine!
@objc func didEnterBackground(_ notification: Notification)
{
// Reset UI once we've left app (but not before).
self.currentOperation = nil
}
}

View File

@@ -1,29 +0,0 @@
//
// Bundle+AltStore.swift
// AltStore
//
// Created by Riley Testut on 5/30/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
public extension Bundle
{
struct Info
{
public static let deviceID = "ALTDeviceID"
public static let serverID = "ALTServerID"
public static let appGroups = "ALTAppGroups"
public static let urlTypes = "CFBundleURLTypes"
}
}
public extension Bundle
{
var infoPlistURL: URL {
let infoPlistURL = self.bundleURL.appendingPathComponent("Info.plist")
return infoPlistURL
}
}

View File

@@ -1,37 +0,0 @@
//
// NSError+ALTServerError.h
// AltStore
//
// Created by Riley Testut on 5/30/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
extern NSErrorDomain const AltServerErrorDomain;
extern NSErrorDomain const AltServerInstallationErrorDomain;
typedef NS_ERROR_ENUM(AltServerErrorDomain, ALTServerError)
{
ALTServerErrorUnknown = 0,
ALTServerErrorConnectionFailed = 1,
ALTServerErrorLostConnection = 2,
ALTServerErrorDeviceNotFound = 3,
ALTServerErrorDeviceWriteFailed = 4,
ALTServerErrorInvalidRequest = 5,
ALTServerErrorInvalidResponse = 6,
ALTServerErrorInvalidApp = 7,
ALTServerErrorInstallationFailed = 8,
ALTServerErrorMaximumFreeAppLimitReached = 9,
ALTServerErrorUnsupportediOSVersion = 10,
};
NS_ASSUME_NONNULL_BEGIN
@interface NSError (ALTServerError)
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,67 +0,0 @@
//
// NSError+ALTServerError.m
// AltStore
//
// Created by Riley Testut on 5/30/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import "NSError+ALTServerError.h"
NSErrorDomain const AltServerErrorDomain = @"com.rileytestut.AltServer";
NSErrorDomain const AltServerInstallationErrorDomain = @"com.rileytestut.AltServer.Installation";
@implementation NSError (ALTServerError)
+ (void)load
{
[NSError setUserInfoValueProviderForDomain:AltServerErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
if ([userInfoKey isEqualToString:NSLocalizedDescriptionKey])
{
return [error alt_localizedDescription];
}
return nil;
}];
}
- (nullable NSString *)alt_localizedDescription
{
switch ((ALTServerError)self.code)
{
case ALTServerErrorUnknown:
return NSLocalizedString(@"An unknown error occured.", @"");
case ALTServerErrorConnectionFailed:
return NSLocalizedString(@"Could not connect to AltServer.", @"");
case ALTServerErrorLostConnection:
return NSLocalizedString(@"Lost connection to AltServer.", @"");
case ALTServerErrorDeviceNotFound:
return NSLocalizedString(@"AltServer could not find this device.", @"");
case ALTServerErrorDeviceWriteFailed:
return NSLocalizedString(@"Failed to write app data to device.", @"");
case ALTServerErrorInvalidRequest:
return NSLocalizedString(@"AltServer received an invalid request.", @"");
case ALTServerErrorInvalidResponse:
return NSLocalizedString(@"AltServer sent an invalid response.", @"");
case ALTServerErrorInvalidApp:
return NSLocalizedString(@"The app is invalid.", @"");
case ALTServerErrorInstallationFailed:
return NSLocalizedString(@"An error occured while installing the app.", @"");
case ALTServerErrorMaximumFreeAppLimitReached:
return NSLocalizedString(@"You have reached the limit of 3 apps per device.", @"");
case ALTServerErrorUnsupportediOSVersion:
return NSLocalizedString(@"Your device must be running iOS 12.2 or later to install AltStore.", @"");
}
}
@end

View File

@@ -1,70 +0,0 @@
//
// ServerProtocol.swift
// AltServer
//
// Created by Riley Testut on 5/24/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
public let ALTServerServiceType = "_altserver._tcp"
// Can only automatically conform ALTServerError.Code to Codable, not ALTServerError itself
extension ALTServerError.Code: Codable {}
protocol ServerMessage: Codable
{
var version: Int { get }
var identifier: String { get }
}
public struct PrepareAppRequest: ServerMessage
{
public var version = 1
public var identifier = "PrepareApp"
public var udid: String
public var contentSize: Int
public init(udid: String, contentSize: Int)
{
self.udid = udid
self.contentSize = contentSize
}
}
public struct BeginInstallationRequest: ServerMessage
{
public var version = 1
public var identifier = "BeginInstallation"
public init()
{
}
}
public struct ServerResponse: ServerMessage
{
public var version = 1
public var identifier = "ServerResponse"
public var progress: Double
public var error: ALTServerError? {
get {
guard let code = self.errorCode else { return nil }
return ALTServerError(code)
}
set {
self.errorCode = newValue?.code
}
}
private var errorCode: ALTServerError.Code?
public init(progress: Double, error: ALTServerError?)
{
self.progress = progress
self.error = error
}
}

View File

@@ -1,5 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "ALTDeviceManager.h"

View File

@@ -1,265 +0,0 @@
//
// AppDelegate.swift
// AltServer
//
// Created by Riley Testut on 5/24/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Cocoa
import UserNotifications
import AltSign
import LaunchAtLogin
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
private var statusItem: NSStatusItem?
private var connectedDevices = [ALTDevice]()
private weak var authenticationAlert: NSAlert?
@IBOutlet private var appMenu: NSMenu!
@IBOutlet private var connectedDevicesMenu: NSMenu!
@IBOutlet private var launchAtLoginMenuItem: NSMenuItem!
private weak var authenticationAppleIDTextField: NSTextField?
private weak var authenticationPasswordTextField: NSSecureTextField?
func applicationDidFinishLaunching(_ aNotification: Notification)
{
UserDefaults.standard.registerDefaults()
UNUserNotificationCenter.current().delegate = self
ConnectionManager.shared.start()
let item = NSStatusBar.system.statusItem(withLength: -1)
guard let button = item.button else { return }
button.image = NSImage(named: "MenuBarIcon")
button.target = self
button.action = #selector(AppDelegate.presentMenu)
self.statusItem = item
self.connectedDevicesMenu.delegate = self
if !UserDefaults.standard.didPresentInitialNotification
{
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("AltServer Running", comment: "")
content.body = NSLocalizedString("AltServer runs in the background as a menu bar app listening for AltStore.", comment: "")
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
UserDefaults.standard.didPresentInitialNotification = true
}
}
func applicationWillTerminate(_ aNotification: Notification)
{
// Insert code here to tear down your application
}
}
private extension AppDelegate
{
@objc func presentMenu()
{
guard let button = self.statusItem?.button, let superview = button.superview, let window = button.window else { return }
self.connectedDevices = ALTDeviceManager.shared.connectedDevices
self.launchAtLoginMenuItem.state = LaunchAtLogin.isEnabled ? .on : .off
self.launchAtLoginMenuItem.action = #selector(AppDelegate.toggleLaunchAtLogin(_:))
let x = button.frame.origin.x
let y = button.frame.origin.y - 5
let location = superview.convert(NSMakePoint(x, y), to: nil)
guard let event = NSEvent.mouseEvent(with: .leftMouseUp, location: location,
modifierFlags: [], timestamp: 0, windowNumber: window.windowNumber, context: nil,
eventNumber: 0, clickCount: 1, pressure: 0)
else { return }
NSMenu.popUpContextMenu(self.appMenu, with: event, for: button)
}
@objc func installAltStore(_ item: NSMenuItem)
{
guard case let index = self.connectedDevicesMenu.index(of: item), index != -1 else { return }
let alert = NSAlert()
alert.messageText = NSLocalizedString("Please enter your Apple ID and password.", comment: "")
alert.informativeText = NSLocalizedString("""
Your Apple ID and password are not saved and are only sent to Apple for authentication.
If you have two-factor authentication enabled, please create an app-specific password for use with AltStore at https://appleid.apple.com.
""", comment: "")
let textFieldSize = NSSize(width: 300, height: 22)
let appleIDTextField = NSTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height))
appleIDTextField.delegate = self
appleIDTextField.translatesAutoresizingMaskIntoConstraints = false
appleIDTextField.placeholderString = NSLocalizedString("Apple ID", comment: "")
alert.window.initialFirstResponder = appleIDTextField
self.authenticationAppleIDTextField = appleIDTextField
let passwordTextField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height))
passwordTextField.delegate = self
passwordTextField.translatesAutoresizingMaskIntoConstraints = false
passwordTextField.placeholderString = NSLocalizedString("Password", comment: "")
self.authenticationPasswordTextField = passwordTextField
appleIDTextField.nextKeyView = passwordTextField
let stackView = NSStackView(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height * 2))
stackView.orientation = .vertical
stackView.distribution = .equalSpacing
stackView.spacing = 0
stackView.addArrangedSubview(appleIDTextField)
stackView.addArrangedSubview(passwordTextField)
alert.accessoryView = stackView
alert.addButton(withTitle: NSLocalizedString("Install", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
self.authenticationAlert = alert
self.validate()
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let response = alert.runModal()
guard response == .alertFirstButtonReturn else { return }
let username = appleIDTextField.stringValue
let password = passwordTextField.stringValue
let device = self.connectedDevices[index]
ALTDeviceManager.shared.installAltStore(to: device, appleID: username, password: password) { (result) in
switch result
{
case .success:
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("Installation Succeeded", comment: "")
content.body = String(format: NSLocalizedString("AltStore was successfully installed on %@.", comment: ""), device.name)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
case .failure(InstallError.cancelled):
// Ignore
break
case .failure(let error as NSError):
let alert = NSAlert()
alert.alertStyle = .critical
alert.messageText = NSLocalizedString("Installation Failed", comment: "")
if let underlyingError = error.userInfo[NSUnderlyingErrorKey] as? Error
{
alert.informativeText = underlyingError.localizedDescription
}
else
{
alert.informativeText = error.localizedDescription
}
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
alert.runModal()
}
}
}
@objc func toggleLaunchAtLogin(_ item: NSMenuItem)
{
if item.state == .on
{
item.state = .off
}
else
{
item.state = .on
}
LaunchAtLogin.isEnabled.toggle()
}
}
extension AppDelegate: NSMenuDelegate
{
func numberOfItems(in menu: NSMenu) -> Int
{
return self.connectedDevices.isEmpty ? 1 : self.connectedDevices.count
}
func menu(_ menu: NSMenu, update item: NSMenuItem, at index: Int, shouldCancel: Bool) -> Bool
{
if self.connectedDevices.isEmpty
{
item.title = NSLocalizedString("No Connected Devices", comment: "")
item.isEnabled = false
item.target = nil
item.action = nil
}
else
{
let device = self.connectedDevices[index]
item.title = device.name
item.isEnabled = true
item.target = self
item.action = #selector(AppDelegate.installAltStore)
item.tag = index
}
return true
}
}
extension AppDelegate: NSTextFieldDelegate
{
func controlTextDidChange(_ obj: Notification)
{
self.validate()
}
func controlTextDidEndEditing(_ obj: Notification)
{
self.validate()
}
private func validate()
{
guard
let appleID = self.authenticationAppleIDTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines),
let password = self.authenticationPasswordTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
else { return }
if appleID.isEmpty || password.isEmpty
{
self.authenticationAlert?.buttons.first?.isEnabled = false
}
else
{
self.authenticationAlert?.buttons.first?.isEnabled = true
}
self.authenticationAlert?.layout()
}
}
extension AppDelegate: UNUserNotificationCenterDelegate
{
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void)
{
completionHandler([.alert, .sound, .badge])
}
}

View File

@@ -1,68 +0,0 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "Icon@16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "Icon@32-1.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "Icon@32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "Icon@64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "Icon@128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "Icon@256-1.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "Icon@256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "Icon@512-1.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "Icon@512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "Icon@1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -1,6 +0,0 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -1,351 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Application-->
<scene sceneID="JPo-4y-FX3">
<objects>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<stackView distribution="fill" orientation="vertical" alignment="leading" spacing="4" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" id="urc-xw-Dhc">
<rect key="frame" x="0.0" y="0.0" width="300" height="48"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zLd-d8-ghZ">
<rect key="frame" x="0.0" y="26" width="300" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Apple ID" drawsBackground="YES" id="BXa-Re-rs3">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="QtW-r2-Vuh"/>
<outlet property="nextKeyView" destination="9rp-Vx-rvB" id="bQY-qj-Sej"/>
</connections>
</textField>
<secureTextField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="9rp-Vx-rvB">
<rect key="frame" x="0.0" y="0.0" width="300" height="22"/>
<secureTextFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Password" drawsBackground="YES" usesSingleLineMode="YES" id="xqJ-wt-DlP">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</secureTextFieldCell>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="qav-xj-izy"/>
</connections>
</secureTextField>
</subviews>
<constraints>
<constraint firstItem="9rp-Vx-rvB" firstAttribute="width" secondItem="urc-xw-Dhc" secondAttribute="width" id="Eht-pU-Gyh"/>
<constraint firstItem="zLd-d8-ghZ" firstAttribute="width" secondItem="urc-xw-Dhc" secondAttribute="width" id="mg7-Kq-abL"/>
<constraint firstAttribute="width" constant="300" id="zqf-x6-BET"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="AltServer" customModuleProvider="target">
<connections>
<outlet property="appMenu" destination="uQy-DD-JDr" id="7cY-Ov-AOW"/>
<outlet property="authenticationAppleIDTextField" destination="zLd-d8-ghZ" id="wW5-0J-zdq"/>
<outlet property="authenticationPasswordTextField" destination="9rp-Vx-rvB" id="ZoC-DI-jzQ"/>
<outlet property="connectedDevicesMenu" destination="KJ9-WY-pW1" id="Mcv-64-iFU"/>
<outlet property="launchAtLoginMenuItem" destination="IyR-FQ-upe" id="Fxn-EP-hwH"/>
</connections>
</customObject>
<application id="hnw-xV-0zn" sceneMemberID="viewController">
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="AltServer" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="AltServer" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About AltServer" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Install AltStore" id="MJ8-Lt-SSV">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Install AltStore" systemMenu="recentDocuments" id="KJ9-WY-pW1">
<items>
<menuItem title="No Connected Devices" id="N5N-3K-XuR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="DKG-yI-Ujv"/>
</connections>
</menuItem>
</items>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="VYb-BL-Zri"/>
</connections>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="1ZZ-BB-xHy"/>
<menuItem title="Launch at Login" id="IyR-FQ-upe" userLabel="Launch At Login">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="mVM-Nm-Zi9"/>
<menuItem title="Quit AltServer" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="Ady-hI-5gd" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="Ady-hI-5gd" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="Ady-hI-5gd" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="Ady-hI-5gd" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="Ady-hI-5gd" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="Ady-hI-5gd" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="Ady-hI-5gd" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="Ady-hI-5gd" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="Ady-hI-5gd" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="Ady-hI-5gd" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="Ady-hI-5gd" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="Ady-hI-5gd" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="Ady-hI-5gd" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="Ady-hI-5gd" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="Ady-hI-5gd" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="Ady-hI-5gd" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="Ady-hI-5gd" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="Ady-hI-5gd" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="Ady-hI-5gd" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="Ady-hI-5gd" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="Ady-hI-5gd" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="Ady-hI-5gd" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="Ady-hI-5gd" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="Ady-hI-5gd" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="Ady-hI-5gd" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="Ady-hI-5gd" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="wpr-3q-Mcd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="AltServer Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections>
</application>
</objects>
<point key="canvasLocation" x="75" y="0.0"/>
</scene>
</scenes>
</document>

View File

@@ -1,444 +0,0 @@
//
// ConnectionManager.swift
// AltServer
//
// Created by Riley Testut on 5/23/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
import Network
import AltKit
extension ALTServerError
{
init<E: Error>(_ error: E)
{
switch error
{
case let error as ALTServerError: self = error
case is DecodingError: self = ALTServerError(.invalidRequest)
case is EncodingError: self = ALTServerError(.invalidResponse)
default:
assertionFailure("Caught unknown error type")
self = ALTServerError(.unknown)
}
}
}
extension ConnectionManager
{
enum State
{
case notRunning
case connecting
case running(NWListener.Service)
case failed(Swift.Error)
}
}
class ConnectionManager
{
static let shared = ConnectionManager()
var stateUpdateHandler: ((State) -> Void)?
private(set) var state: State = .notRunning {
didSet {
self.stateUpdateHandler?(self.state)
}
}
private lazy var listener = self.makeListener()
private let dispatchQueue = DispatchQueue(label: "com.rileytestut.AltServer.connections", qos: .utility)
private var connections = [NWConnection]()
private init()
{
}
func start()
{
switch self.state
{
case .notRunning, .failed: self.listener.start(queue: self.dispatchQueue)
default: break
}
}
func stop()
{
switch self.state
{
case .running: self.listener.cancel()
default: break
}
}
}
private extension ConnectionManager
{
func makeListener() -> NWListener
{
let listener = try! NWListener(using: .tcp)
let service: NWListener.Service
if let serverID = UserDefaults.standard.serverID?.data(using: .utf8)
{
let txtDictionary = ["serverID": serverID]
let txtData = NetService.data(fromTXTRecord: txtDictionary)
service = NWListener.Service(name: nil, type: ALTServerServiceType, domain: nil, txtRecord: txtData)
}
else
{
service = NWListener.Service(type: ALTServerServiceType)
}
listener.service = service
listener.serviceRegistrationUpdateHandler = { (serviceChange) in
switch serviceChange
{
case .add(.service(let name, let type, let domain, _)):
let service = NWListener.Service(name: name, type: type, domain: domain, txtRecord: nil)
self.state = .running(service)
default: break
}
}
listener.stateUpdateHandler = { (state) in
switch state
{
case .ready: break
case .waiting, .setup: self.state = .connecting
case .cancelled: self.state = .notRunning
case .failed(let error):
self.state = .failed(error)
self.start()
@unknown default: break
}
}
listener.newConnectionHandler = { [weak self] (connection) in
self?.awaitRequest(from: connection)
}
return listener
}
func disconnect(_ connection: NWConnection)
{
switch connection.state
{
case .cancelled, .failed:
print("Disconnecting from \(connection.endpoint)...")
if let index = self.connections.firstIndex(where: { $0 === connection })
{
self.connections.remove(at: index)
}
default:
// State update handler will call this method again.
connection.cancel()
}
}
func process(data: Data?, error: NWError?, from connection: NWConnection) throws -> Data
{
do
{
do
{
guard let data = data else { throw error ?? ALTServerError(.unknown) }
return data
}
catch let error as NWError
{
print("Error receiving data from connection \(connection)", error)
throw ALTServerError(.lostConnection)
}
catch
{
throw error
}
}
catch let error as ALTServerError
{
throw error
}
catch
{
preconditionFailure("A non-ALTServerError should never be thrown from this method.")
}
}
}
private extension ConnectionManager
{
func awaitRequest(from connection: NWConnection)
{
guard !self.connections.contains(where: { $0 === connection }) else { return }
self.connections.append(connection)
connection.stateUpdateHandler = { [weak self] (state) in
switch state
{
case .setup, .preparing: break
case .ready:
print("Connected to client:", connection.endpoint)
self?.receiveApp(from: connection) { (result) in
self?.finish(connection: connection, error: result.error)
}
case .waiting:
print("Waiting for connection...")
case .failed(let error):
print("Failed to connect to service \(connection.endpoint).", error)
self?.disconnect(connection)
case .cancelled:
self?.disconnect(connection)
@unknown default: break
}
}
connection.start(queue: self.dispatchQueue)
}
func receiveApp(from connection: NWConnection, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
{
var temporaryURL: URL?
func finish(_ result: Result<Void, ALTServerError>)
{
if let temporaryURL = temporaryURL
{
do { try FileManager.default.removeItem(at: temporaryURL) }
catch { print("Failed to remove .ipa.", error) }
}
completionHandler(result)
}
self.receive(PrepareAppRequest.self, from: connection) { (result) in
print("Received request with result:", result)
switch result
{
case .failure(let error): finish(.failure(error))
case .success(let request):
self.receiveApp(for: request, from: connection) { (result) in
print("Received app with result:", result)
switch result
{
case .failure(let error): finish(.failure(error))
case .success(let request, let fileURL):
temporaryURL = fileURL
print("Awaiting begin installation request...")
self.receive(BeginInstallationRequest.self, from: connection) { (result) in
print("Received begin installation request with result:", result)
switch result
{
case .failure(let error): finish(.failure(error))
case .success:
print("Installing to device \(request.udid)...")
self.installApp(at: fileURL, toDeviceWithUDID: request.udid, connection: connection) { (result) in
print("Installed to device with result:", result)
switch result
{
case .failure(let error): finish(.failure(error))
case .success: finish(.success(()))
}
}
}
}
}
}
}
}
}
func finish(connection: NWConnection, error: ALTServerError?)
{
if let error = error
{
print("Failed to process request from \(connection.endpoint).", error)
}
else
{
print("Processed request from \(connection.endpoint).")
}
let response = ServerResponse(progress: 1.0, error: error)
self.send(response, to: connection) { (result) in
print("Sent response to \(connection.endpoint) with result:", result)
self.disconnect(connection)
}
}
func receiveApp(for request: PrepareAppRequest, from connection: NWConnection, completionHandler: @escaping (Result<(PrepareAppRequest, URL), ALTServerError>) -> Void)
{
connection.receive(minimumIncompleteLength: request.contentSize, maximumLength: request.contentSize) { (data, _, _, error) in
do
{
print("Received app data!")
let data = try self.process(data: data, error: error, from: connection)
print("Processed app data!")
guard ALTDeviceManager.shared.availableDevices.contains(where: { $0.identifier == request.udid }) else { throw ALTServerError(.deviceNotFound) }
print("Writing app data...")
let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".ipa")
try data.write(to: temporaryURL, options: .atomic)
print("Wrote app to URL:", temporaryURL)
completionHandler(.success((request, temporaryURL)))
}
catch
{
print("Error processing app data:", error)
completionHandler(.failure(ALTServerError(error)))
}
}
}
func installApp(at fileURL: URL, toDeviceWithUDID udid: String, connection: NWConnection, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
{
let serialQueue = DispatchQueue(label: "com.altstore.ConnectionManager.installQueue", qos: .default)
var isSending = false
var observation: NSKeyValueObservation?
let progress = ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: udid) { (success, error) in
print("Installed app with result:", error == nil ? "Success" : error!.localizedDescription)
if let error = error.map({ $0 as? ALTServerError ?? ALTServerError(.unknown) })
{
completionHandler(.failure(error))
}
else
{
completionHandler(.success(()))
}
observation?.invalidate()
observation = nil
}
observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, change) in
serialQueue.async {
guard !isSending else { return }
isSending = true
print("Progress:", progress.fractionCompleted)
let response = ServerResponse(progress: progress.fractionCompleted, error: nil)
self.send(response, to: connection) { (result) in
serialQueue.async {
isSending = false
}
}
}
})
}
func send<T: Encodable>(_ response: T, to connection: NWConnection, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
{
do
{
let data = try JSONEncoder().encode(response)
let responseSize = withUnsafeBytes(of: Int32(data.count)) { Data($0) }
connection.send(content: responseSize, completion: .contentProcessed { (error) in
do
{
if let error = error
{
throw error
}
connection.send(content: data, completion: .contentProcessed { (error) in
if error != nil
{
completionHandler(.failure(.init(.lostConnection)))
}
else
{
completionHandler(.success(()))
}
})
}
catch
{
completionHandler(.failure(.init(.lostConnection)))
}
})
}
catch
{
completionHandler(.failure(.init(.invalidResponse)))
}
}
func receive<T: Decodable>(_ responseType: T.Type, from connection: NWConnection, completionHandler: @escaping (Result<T, ALTServerError>) -> Void)
{
let size = MemoryLayout<Int32>.size
print("Receiving request size")
connection.receive(minimumIncompleteLength: size, maximumLength: size) { (data, _, _, error) in
do
{
let data = try self.process(data: data, error: error, from: connection)
print("Receiving request...")
let expectedBytes = Int(data.withUnsafeBytes { $0.load(as: Int32.self) })
connection.receive(minimumIncompleteLength: expectedBytes, maximumLength: expectedBytes) { (data, _, _, error) in
do
{
let data = try self.process(data: data, error: error, from: connection)
let request = try JSONDecoder().decode(T.self, from: data)
print("Received installation request:", request)
completionHandler(.success(request))
}
catch
{
completionHandler(.failure(ALTServerError(error)))
}
}
}
catch
{
completionHandler(.failure(ALTServerError(error)))
}
}
}
}

View File

@@ -1,434 +0,0 @@
//
// ALTDeviceManager+Installation.swift
// AltServer
//
// Created by Riley Testut on 7/1/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Cocoa
import UserNotifications
enum InstallError: LocalizedError
{
case cancelled
case noTeam
case missingPrivateKey
case missingCertificate
var errorDescription: String? {
switch self
{
case .cancelled: return NSLocalizedString("The operation was cancelled.", comment: "")
case .noTeam: return "You are not a member of any developer teams."
case .missingPrivateKey: return "The developer certificate's private key could not be found."
case .missingCertificate: return "The developer certificate could not be found."
}
}
}
extension ALTDeviceManager
{
func installAltStore(to device: ALTDevice, appleID: String, password: String, completion: @escaping (Result<Void, Error>) -> Void)
{
let destinationDirectoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
func finish(_ error: Error?, title: String = "")
{
DispatchQueue.main.async {
if let error = error
{
completion(.failure(error))
}
else
{
completion(.success(()))
}
}
try? FileManager.default.removeItem(at: destinationDirectoryURL)
}
self.authenticate(appleID: appleID, password: password) { (result) in
do
{
let account = try result.get()
self.fetchTeam(for: account) { (result) in
do
{
let team = try result.get()
self.register(device, team: team) { (result) in
do
{
let device = try result.get()
self.fetchCertificate(for: team) { (result) in
do
{
let certificate = try result.get()
let content = UNMutableNotificationContent()
content.title = String(format: NSLocalizedString("Installing AltStore to %@...", comment: ""), device.name)
content.body = NSLocalizedString("This may take a few seconds.", comment: "")
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
self.downloadApp { (result) in
do
{
let fileURL = try result.get()
try FileManager.default.createDirectory(at: destinationDirectoryURL, withIntermediateDirectories: true, attributes: nil)
let appBundleURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: destinationDirectoryURL)
do
{
try FileManager.default.removeItem(at: fileURL)
}
catch
{
print("Failed to remove downloaded .ipa.", error)
}
guard let application = ALTApplication(fileURL: appBundleURL) else { throw ALTError(.invalidApp) }
self.registerAppID(name: "AltStore", identifier: "com.rileytestut.AltStore", team: team) { (result) in
do
{
let appID = try result.get()
self.updateFeatures(for: appID, app: application, team: team) { (result) in
do
{
let appID = try result.get()
self.fetchProvisioningProfile(for: appID, team: team) { (result) in
do
{
let provisioningProfile = try result.get()
self.install(application, to: device, team: team, appID: appID, certificate: certificate, profile: provisioningProfile) { (result) in
finish(result.error, title: "Failed to Install AltStore")
}
}
catch
{
finish(error, title: "Failed to Fetch Provisioning Profile")
}
}
}
catch
{
finish(error, title: "Failed to Update App ID")
}
}
}
catch
{
finish(error, title: "Failed to Register App")
}
}
}
catch
{
finish(error, title: "Failed to Download AltStore")
return
}
}
}
catch
{
finish(error, title: "Failed to Fetch Certificate")
}
}
}
catch
{
finish(error, title: "Failed to Register Device")
}
}
}
catch
{
finish(error, title: "Failed to Fetch Team")
}
}
}
catch
{
finish(error, title: "Failed to Authenticate")
}
}
}
func downloadApp(completionHandler: @escaping (Result<URL, Error>) -> Void)
{
let appURL = URL(string: "https://f000.backblazeb2.com/file/altstore/altstore.ipa")!
let downloadTask = URLSession.shared.downloadTask(with: appURL) { (fileURL, response, error) in
do
{
let (fileURL, _) = try Result((fileURL, response), error).get()
completionHandler(.success(fileURL))
}
catch
{
completionHandler(.failure(error))
}
}
downloadTask.resume()
}
func authenticate(appleID: String, password: String, completionHandler: @escaping (Result<ALTAccount, Error>) -> Void)
{
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password) { (account, error) in
let result = Result(account, error)
completionHandler(result)
}
}
func fetchTeam(for account: ALTAccount, completionHandler: @escaping (Result<ALTTeam, Error>) -> Void)
{
ALTAppleAPI.shared.fetchTeams(for: account) { (teams, error) in
do
{
let teams = try Result(teams, error).get()
if let team = teams.first(where: { $0.type == .free })
{
return completionHandler(.success(team))
}
else if let team = teams.first(where: { $0.type == .individual })
{
return completionHandler(.success(team))
}
else if let team = teams.first
{
return completionHandler(.success(team))
}
else
{
throw InstallError.noTeam
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func fetchCertificate(for team: ALTTeam, completionHandler: @escaping (Result<ALTCertificate, Error>) -> Void)
{
ALTAppleAPI.shared.fetchCertificates(for: team) { (certificates, error) in
do
{
let certificates = try Result(certificates, error).get()
// Check if there is another AltStore certificate, which means AltStore has been installed with this Apple ID before.
if certificates.contains(where: { $0.machineName?.starts(with: "AltStore") == true })
{
var isCancelled = false
DispatchQueue.main.sync {
let alert = NSAlert()
alert.messageText = NSLocalizedString("AltStore already installed on another device.", comment: "")
alert.informativeText = NSLocalizedString("Apps installed with AltStore on your other devices will stop working. Are you sure you want to continue?", comment: "")
alert.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let buttonIndex = alert.runModal()
if buttonIndex == NSApplication.ModalResponse.alertSecondButtonReturn
{
isCancelled = true
}
}
if isCancelled
{
return completionHandler(.failure(InstallError.cancelled))
}
}
if let certificate = certificates.first
{
ALTAppleAPI.shared.revoke(certificate, for: team) { (success, error) in
do
{
try Result(success, error).get()
self.fetchCertificate(for: team, completionHandler: completionHandler)
}
catch
{
completionHandler(.failure(error))
}
}
}
else
{
ALTAppleAPI.shared.addCertificate(machineName: "AltStore", to: team) { (certificate, error) in
do
{
let certificate = try Result(certificate, error).get()
guard let privateKey = certificate.privateKey else { throw InstallError.missingPrivateKey }
ALTAppleAPI.shared.fetchCertificates(for: team) { (certificates, error) in
do
{
let certificates = try Result(certificates, error).get()
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else {
throw InstallError.missingCertificate
}
certificate.privateKey = privateKey
completionHandler(.success(certificate))
}
catch
{
completionHandler(.failure(error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func registerAppID(name appName: String, identifier: String, team: ALTTeam, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
{
let bundleID = "com.\(team.identifier).\(identifier)"
ALTAppleAPI.shared.fetchAppIDs(for: team) { (appIDs, error) in
do
{
let appIDs = try Result(appIDs, error).get()
if let appID = appIDs.first(where: { $0.bundleIdentifier == bundleID })
{
completionHandler(.success(appID))
}
else
{
ALTAppleAPI.shared.addAppID(withName: appName, bundleIdentifier: bundleID, team: team) { (appID, error) in
completionHandler(Result(appID, error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func updateFeatures(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
{
let requiredFeatures = app.entitlements.compactMap { (entitlement, value) -> (ALTFeature, Any)? in
guard let feature = ALTFeature(entitlement) else { return nil }
return (feature, value)
}
var features = requiredFeatures.reduce(into: [ALTFeature: Any]()) { $0[$1.0] = $1.1 }
if let applicationGroups = app.entitlements[.appGroups] as? [String], !applicationGroups.isEmpty
{
features[.appGroups] = true
}
let appID = appID.copy() as! ALTAppID
appID.features = features
ALTAppleAPI.shared.update(appID, team: team) { (appID, error) in
completionHandler(Result(appID, error))
}
}
func register(_ device: ALTDevice, team: ALTTeam, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
{
ALTAppleAPI.shared.fetchDevices(for: team) { (devices, error) in
do
{
let devices = try Result(devices, error).get()
if let device = devices.first(where: { $0.identifier == device.identifier })
{
completionHandler(.success(device))
}
else
{
ALTAppleAPI.shared.registerDevice(name: device.name, identifier: device.identifier, team: team) { (device, error) in
completionHandler(Result(device, error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func fetchProvisioningProfile(for appID: ALTAppID, team: ALTTeam, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
{
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, team: team) { (profile, error) in
completionHandler(Result(profile, error))
}
}
func install(_ application: ALTApplication, to device: ALTDevice, team: ALTTeam, appID: ALTAppID, certificate: ALTCertificate, profile: ALTProvisioningProfile, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
DispatchQueue.global().async {
do
{
let infoPlistURL = application.fileURL.appendingPathComponent("Info.plist")
guard var infoDictionary = NSDictionary(contentsOf: infoPlistURL) as? [String: Any] else { throw ALTError(.missingInfoPlist) }
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
infoDictionary[Bundle.Info.deviceID] = device.identifier
infoDictionary[Bundle.Info.serverID] = UserDefaults.standard.serverID
try (infoDictionary as NSDictionary).write(to: infoPlistURL)
let resigner = ALTSigner(team: team, certificate: certificate)
resigner.signApp(at: application.fileURL, provisioningProfiles: [profile]) { (success, error) in
do
{
try Result(success, error).get()
ALTDeviceManager.shared.installApp(at: application.fileURL, toDeviceWithUDID: device.identifier) { (success, error) in
completionHandler(Result(success, error))
}
}
catch
{
print("Failed to install app", error)
completionHandler(.failure(error))
}
}
}
catch
{
print("Failed to install AltStore", error)
completionHandler(.failure(error))
}
}
}
}

View File

@@ -1,25 +0,0 @@
//
// ALTDeviceManager.h
// AltServer
//
// Created by Riley Testut on 5/24/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AltSign/AltSign.h>
NS_ASSUME_NONNULL_BEGIN
@interface ALTDeviceManager : NSObject
@property (class, nonatomic, readonly) ALTDeviceManager *sharedManager;
@property (nonatomic, readonly) NSArray<ALTDevice *> *connectedDevices;
@property (nonatomic, readonly) NSArray<ALTDevice *> *availableDevices;
- (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,672 +0,0 @@
//
// ALTDeviceManager.m
// AltServer
//
// Created by Riley Testut on 5/24/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import "ALTDeviceManager.h"
#import "NSError+ALTServerError.h"
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
#include <libimobiledevice/installation_proxy.h>
#include <libimobiledevice/notification_proxy.h>
#include <libimobiledevice/afc.h>
#include <libimobiledevice/misagent.h>
void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *udid);
NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
@interface ALTDeviceManager ()
@property (nonatomic, readonly) NSMutableDictionary<NSUUID *, void (^)(NSError *)> *installationCompletionHandlers;
@property (nonatomic, readonly) NSMutableDictionary<NSUUID *, NSProgress *> *installationProgress;
@property (nonatomic, readonly) dispatch_queue_t installationQueue;
@end
@implementation ALTDeviceManager
+ (ALTDeviceManager *)sharedManager
{
static ALTDeviceManager *_manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_manager = [[self alloc] init];
});
return _manager;
}
- (instancetype)init
{
self = [super init];
if (self)
{
_installationCompletionHandlers = [NSMutableDictionary dictionary];
_installationProgress = [NSMutableDictionary dictionary];
_installationQueue = dispatch_queue_create("com.rileytestut.AltServer.InstallationQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
{
NSProgress *progress = [NSProgress discreteProgressWithTotalUnitCount:4];
dispatch_async(self.installationQueue, ^{
NSUUID *UUID = [NSUUID UUID];
__block char *uuidString = (char *)malloc(UUID.UUIDString.length + 1);
strncpy(uuidString, (const char *)UUID.UUIDString.UTF8String, UUID.UUIDString.length);
uuidString[UUID.UUIDString.length] = '\0';
__block idevice_t device = NULL;
__block lockdownd_client_t client = NULL;
__block instproxy_client_t ipc = NULL;
__block afc_client_t afc = NULL;
__block misagent_client_t mis = NULL;
__block lockdownd_service_descriptor_t service = NULL;
NSURL *removedProfilesDirectoryURL = [[[NSFileManager defaultManager] temporaryDirectory] URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
NSMutableDictionary<NSString *, ALTProvisioningProfile *> *preferredProfiles = [NSMutableDictionary dictionary];
void (^finish)(NSError *error) = ^(NSError *error) {
if ([[NSFileManager defaultManager] fileExistsAtPath:removedProfilesDirectoryURL.path isDirectory:nil])
{
// Reinstall all provisioning profiles we removed before installation.
NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:removedProfilesDirectoryURL.path error:nil];
for (NSString *filename in contents)
{
NSURL *fileURL = [removedProfilesDirectoryURL URLByAppendingPathComponent:filename];
ALTProvisioningProfile *provisioningProfile = [[ALTProvisioningProfile alloc] initWithURL:fileURL];
if (provisioningProfile == nil)
{
continue;
}
ALTProvisioningProfile *preferredProfile = preferredProfiles[provisioningProfile.bundleIdentifier];
if (![preferredProfile isEqual:provisioningProfile])
{
continue;
}
plist_t pdata = plist_new_data((const char *)provisioningProfile.data.bytes, provisioningProfile.data.length);
if (misagent_install(mis, pdata) == MISAGENT_E_SUCCESS)
{
NSLog(@"Reinstalled profile: %@", provisioningProfile.UUID);
}
else
{
int code = misagent_get_status_code(mis);
NSLog(@"Failed to reinstall provisioning profile %@. (%@)", provisioningProfile.UUID, @(code));
}
}
[[NSFileManager defaultManager] removeItemAtURL:removedProfilesDirectoryURL error:nil];
}
instproxy_client_free(ipc);
afc_client_free(afc);
lockdownd_client_free(client);
misagent_client_free(mis);
idevice_free(device);
lockdownd_service_descriptor_free(service);
free(uuidString);
uuidString = NULL;
if (error != nil)
{
completionHandler(NO, error);
}
else
{
completionHandler(YES, nil);
}
};
NSURL *appBundleURL = nil;
NSURL *temporaryDirectoryURL = nil;
if ([fileURL.pathExtension.lowercaseString isEqualToString:@"app"])
{
appBundleURL = fileURL;
temporaryDirectoryURL = nil;
}
else if ([fileURL.pathExtension.lowercaseString isEqualToString:@"ipa"])
{
NSLog(@"Unzipping .ipa...");
temporaryDirectoryURL = [NSFileManager.defaultManager.temporaryDirectory URLByAppendingPathComponent:[[NSUUID UUID] UUIDString] isDirectory:YES];
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtURL:temporaryDirectoryURL withIntermediateDirectories:YES attributes:nil error:&error])
{
return finish(error);
}
appBundleURL = [[NSFileManager defaultManager] unzipAppBundleAtURL:fileURL toDirectory:temporaryDirectoryURL error:&error];
if (appBundleURL == nil)
{
return finish(error);
}
}
else
{
return finish([NSError errorWithDomain:NSCocoaErrorDomain code:NSFileReadCorruptFileError userInfo:@{NSURLErrorKey: fileURL}]);
}
/* Find Device */
if (idevice_new(&device, udid.UTF8String) != IDEVICE_E_SUCCESS)
{
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]);
}
/* Connect to Device */
if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS)
{
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
}
/* Connect to Installation Proxy */
if ((lockdownd_start_service(client, "com.apple.mobile.installation_proxy", &service) != LOCKDOWN_E_SUCCESS) || service == NULL)
{
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
}
if (instproxy_client_new(device, service, &ipc) != INSTPROXY_E_SUCCESS)
{
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
}
if (service)
{
lockdownd_service_descriptor_free(service);
service = NULL;
}
/* Connect to Misagent */
// Must connect now, since if we take too long writing files to device, connecting may fail later when managing profiles.
if (lockdownd_start_service(client, "com.apple.misagent", &service) != LOCKDOWN_E_SUCCESS || service == NULL)
{
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
}
if (misagent_client_new(device, service, &mis) != MISAGENT_E_SUCCESS)
{
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
}
/* Connect to AFC service */
if ((lockdownd_start_service(client, "com.apple.afc", &service) != LOCKDOWN_E_SUCCESS) || service == NULL)
{
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
}
if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS)
{
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
}
NSURL *stagingURL = [NSURL fileURLWithPath:@"PublicStaging" isDirectory:YES];
/* Prepare for installation */
char **files = NULL;
if (afc_get_file_info(afc, stagingURL.relativePath.fileSystemRepresentation, &files) != AFC_E_SUCCESS)
{
if (afc_make_directory(afc, stagingURL.relativePath.fileSystemRepresentation) != AFC_E_SUCCESS)
{
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceWriteFailed userInfo:nil]);
}
}
if (files)
{
int i = 0;
while (files[i])
{
free(files[i]);
i++;
}
free(files);
}
NSLog(@"Writing to device...");
plist_t options = instproxy_client_options_new();
instproxy_client_options_add(options, "PackageType", "Developer", NULL);
NSURL *destinationURL = [stagingURL URLByAppendingPathComponent:appBundleURL.lastPathComponent];
// Writing files to device should be worth 3/4 of total work.
[progress becomeCurrentWithPendingUnitCount:3];
NSError *writeError = nil;
if (![self writeDirectory:appBundleURL toDestinationURL:destinationURL client:afc progress:nil error:&writeError])
{
return finish(writeError);
}
NSLog(@"Finished writing to device.");
if (service)
{
lockdownd_service_descriptor_free(service);
service = NULL;
}
/* Provisioning Profiles */
NSURL *provisioningProfileURL = [appBundleURL URLByAppendingPathComponent:@"embedded.mobileprovision"];
ALTProvisioningProfile *installationProvisioningProfile = [[ALTProvisioningProfile alloc] initWithURL:provisioningProfileURL];
if (installationProvisioningProfile != nil)
{
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtURL:removedProfilesDirectoryURL withIntermediateDirectories:YES attributes:nil error:&error])
{
return finish(error);
}
plist_t profiles = NULL;
if (misagent_copy_all(mis, &profiles) != MISAGENT_E_SUCCESS)
{
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
}
uint32_t profileCount = plist_array_get_size(profiles);
for (int i = 0; i < profileCount; i++)
{
plist_t profile = plist_array_get_item(profiles, i);
if (plist_get_node_type(profile) != PLIST_DATA)
{
continue;
}
char *bytes = NULL;
uint64_t length = 0;
plist_get_data_val(profile, &bytes, &length);
if (bytes == NULL)
{
continue;
}
NSData *data = [NSData dataWithBytes:(const void *)bytes length:length];
ALTProvisioningProfile *provisioningProfile = [[ALTProvisioningProfile alloc] initWithData:data];
if (![provisioningProfile.teamIdentifier isEqualToString:installationProvisioningProfile.teamIdentifier])
{
NSLog(@"Ignoring: %@ (Team: %@)", provisioningProfile.bundleIdentifier, provisioningProfile.teamIdentifier);
continue;
}
ALTProvisioningProfile *preferredProfile = preferredProfiles[provisioningProfile.bundleIdentifier];
if (preferredProfile != nil)
{
if ([provisioningProfile.expirationDate compare:preferredProfile.expirationDate] == NSOrderedDescending)
{
preferredProfiles[provisioningProfile.bundleIdentifier] = provisioningProfile;
}
}
else
{
preferredProfiles[provisioningProfile.bundleIdentifier] = provisioningProfile;
}
NSString *filename = [NSString stringWithFormat:@"%@.mobileprovision", [[NSUUID UUID] UUIDString]];
NSURL *fileURL = [removedProfilesDirectoryURL URLByAppendingPathComponent:filename];
NSError *copyError = nil;
if (![provisioningProfile.data writeToURL:fileURL options:NSDataWritingAtomic error:&copyError])
{
NSLog(@"Failed to copy profile to temporary URL. %@", copyError);
continue;
}
if (misagent_remove(mis, provisioningProfile.UUID.UUIDString.lowercaseString.UTF8String) == MISAGENT_E_SUCCESS)
{
NSLog(@"Removed provisioning profile: %@", provisioningProfile.UUID);
}
else
{
int code = misagent_get_status_code(mis);
NSLog(@"Failed to remove provisioning profile %@. Error Code: %@", provisioningProfile.UUID, @(code));
}
}
lockdownd_client_free(client);
client = NULL;
}
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSProgress *installationProgress = [NSProgress progressWithTotalUnitCount:100 parent:progress pendingUnitCount:1];
self.installationProgress[UUID] = installationProgress;
self.installationCompletionHandlers[UUID] = ^(NSError *error) {
finish(error);
if (temporaryDirectoryURL != nil)
{
NSError *error = nil;
if (![[NSFileManager defaultManager] removeItemAtURL:temporaryDirectoryURL error:&error])
{
NSLog(@"Error removing temporary directory. %@", error);
}
}
dispatch_semaphore_signal(semaphore);
};
NSLog(@"Installing to device %@...", udid);
instproxy_install(ipc, destinationURL.relativePath.fileSystemRepresentation, options, ALTDeviceManagerUpdateStatus, uuidString);
instproxy_client_options_free(options);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
return progress;
}
- (BOOL)writeDirectory:(NSURL *)directoryURL toDestinationURL:(NSURL *)destinationURL client:(afc_client_t)afc progress:(NSProgress *)progress error:(NSError **)error
{
afc_make_directory(afc, destinationURL.relativePath.fileSystemRepresentation);
if (progress == nil)
{
NSDirectoryEnumerator *countEnumerator = [[NSFileManager defaultManager] enumeratorAtURL:directoryURL
includingPropertiesForKeys:@[]
options:0
errorHandler:^BOOL(NSURL * _Nonnull url, NSError * _Nonnull error) {
if (error) {
NSLog(@"[Error] %@ (%@)", error, url);
return NO;
}
return YES;
}];
NSInteger totalCount = 0;
for (NSURL *__unused fileURL in countEnumerator)
{
totalCount++;
}
progress = [NSProgress progressWithTotalUnitCount:totalCount];
}
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:directoryURL
includingPropertiesForKeys:@[NSURLIsDirectoryKey]
options:NSDirectoryEnumerationSkipsSubdirectoryDescendants
errorHandler:^BOOL(NSURL * _Nonnull url, NSError * _Nonnull error) {
if (error) {
NSLog(@"[Error] %@ (%@)", error, url);
return NO;
}
return YES;
}];
for (NSURL *fileURL in enumerator)
{
NSNumber *isDirectory = nil;
if (![fileURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:error])
{
return NO;
}
if ([isDirectory boolValue])
{
NSURL *destinationDirectoryURL = [destinationURL URLByAppendingPathComponent:fileURL.lastPathComponent isDirectory:YES];
if (![self writeDirectory:fileURL toDestinationURL:destinationDirectoryURL client:afc progress:progress error:error])
{
return NO;
}
}
else
{
NSURL *destinationFileURL = [destinationURL URLByAppendingPathComponent:fileURL.lastPathComponent isDirectory:NO];
if (![self writeFile:fileURL toDestinationURL:destinationFileURL client:afc error:error])
{
return NO;
}
}
progress.completedUnitCount += 1;
}
return YES;
}
- (BOOL)writeFile:(NSURL *)fileURL toDestinationURL:(NSURL *)destinationURL client:(afc_client_t)afc error:(NSError **)error
{
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:fileURL.path];
if (fileHandle == nil)
{
if (error)
{
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileNoSuchFileError userInfo:@{NSURLErrorKey: fileURL}];
}
return NO;
}
NSData *data = [fileHandle readDataToEndOfFile];
uint64_t af = 0;
if ((afc_file_open(afc, destinationURL.relativePath.fileSystemRepresentation, AFC_FOPEN_WRONLY, &af) != AFC_E_SUCCESS) || af == 0)
{
if (error)
{
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSURLErrorKey: destinationURL}];
}
return NO;
}
BOOL success = YES;
uint32_t bytesWritten = 0;
while (bytesWritten < data.length)
{
uint32_t count = 0;
if (afc_file_write(afc, af, (const char *)data.bytes + bytesWritten, (uint32_t)data.length - bytesWritten, &count) != AFC_E_SUCCESS)
{
if (error)
{
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSURLErrorKey: destinationURL}];
}
success = NO;
break;
}
bytesWritten += count;
}
if (bytesWritten != data.length)
{
if (error)
{
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSURLErrorKey: destinationURL}];
}
success = NO;
}
afc_file_close(afc, af);
return success;
}
#pragma mark - Getters -
- (NSArray<ALTDevice *> *)connectedDevices
{
return [self availableDevicesIncludingNetworkDevices:NO];
}
- (NSArray<ALTDevice *> *)availableDevices
{
return [self availableDevicesIncludingNetworkDevices:YES];
}
- (NSArray<ALTDevice *> *)availableDevicesIncludingNetworkDevices:(BOOL)includingNetworkDevices
{
NSMutableSet *connectedDevices = [NSMutableSet set];
int count = 0;
char **udids = NULL;
if (idevice_get_device_list(&udids, &count) < 0)
{
fprintf(stderr, "ERROR: Unable to retrieve device list!\n");
return @[];
}
for (int i = 0; i < count; i++)
{
char *udid = udids[i];
idevice_t device = NULL;
if (includingNetworkDevices)
{
idevice_new(&device, udid);
}
else
{
idevice_new_ignore_network(&device, udid);
}
if (!device)
{
continue;
}
lockdownd_client_t client = NULL;
int result = lockdownd_client_new(device, &client, "altserver");
if (result != LOCKDOWN_E_SUCCESS)
{
fprintf(stderr, "ERROR: Connecting to device %s failed! (%d)\n", udid, result);
idevice_free(device);
continue;
}
char *device_name = NULL;
if (lockdownd_get_device_name(client, &device_name) != LOCKDOWN_E_SUCCESS || device_name == NULL)
{
fprintf(stderr, "ERROR: Could not get device name!\n");
lockdownd_client_free(client);
idevice_free(device);
continue;
}
lockdownd_client_free(client);
idevice_free(device);
NSString *name = [NSString stringWithCString:device_name encoding:NSUTF8StringEncoding];
NSString *identifier = [NSString stringWithCString:udid encoding:NSUTF8StringEncoding];
ALTDevice *altDevice = [[ALTDevice alloc] initWithName:name identifier:identifier];
[connectedDevices addObject:altDevice];
if (device_name != NULL)
{
free(device_name);
}
}
idevice_device_list_free(udids);
return connectedDevices.allObjects;
}
@end
#pragma mark - Callbacks -
void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *uuid)
{
NSUUID *UUID = [[NSUUID alloc] initWithUUIDString:[NSString stringWithUTF8String:(const char *)uuid]];
NSProgress *progress = ALTDeviceManager.sharedManager.installationProgress[UUID];
if (progress == nil)
{
return;
}
int percent = -1;
instproxy_status_get_percent_complete(status, &percent);
char *name = NULL;
char *description = NULL;
uint64_t code = 0;
instproxy_status_get_error(status, &name, &description, &code);
if ((percent == -1 && progress.completedUnitCount > 0) || code != 0 || name != NULL)
{
void (^completionHandler)(NSError *) = ALTDeviceManager.sharedManager.installationCompletionHandlers[UUID];
if (completionHandler != nil)
{
if (code != 0 || name != NULL)
{
NSLog(@"Error installing app. %@ (%@). %@", @(code), @(name), @(description));
NSError *error = nil;
if (code == 3892346913)
{
error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorMaximumFreeAppLimitReached userInfo:nil];
}
else
{
NSString *errorName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
if ([errorName isEqualToString:@"DeviceOSVersionTooLow"])
{
error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnsupportediOSVersion userInfo:nil];
}
else
{
NSError *underlyingError = [NSError errorWithDomain:AltServerInstallationErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: @(description)}];
error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorInstallationFailed userInfo:@{NSUnderlyingErrorKey: underlyingError}];
}
}
completionHandler(error);
}
else
{
NSLog(@"Finished installing app!");
completionHandler(nil);
}
ALTDeviceManager.sharedManager.installationCompletionHandlers[UUID] = nil;
ALTDeviceManager.sharedManager.installationProgress[UUID] = nil;
}
}
else if (progress.completedUnitCount < percent)
{
progress.completedUnitCount = percent;
NSLog(@"Installation Progress: %@", @(percent));
}
}

View File

@@ -1,38 +0,0 @@
//
// UserDefaults+AltServer.swift
// AltServer
//
// Created by Riley Testut on 7/31/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
extension UserDefaults
{
var serverID: String? {
get {
return self.string(forKey: "serverID")
}
set {
self.set(newValue, forKey: "serverID")
}
}
var didPresentInitialNotification: Bool {
get {
return self.bool(forKey: "didPresentInitialNotification")
}
set {
self.set(newValue, forKey: "didPresentInitialNotification")
}
}
func registerDefaults()
{
if self.serverID == nil
{
self.serverID = UUID().uuidString
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:AltStore.xcodeproj">
location = "self:">
</FileRef>
</Workspace>

View File

@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0940"
version = "1.3">
LastUpgradeVersion = "1610"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
@@ -14,10 +15,10 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E32E9B731EB87EA3000FEEE9"
BuildableName = "LaunchAtLoginHelper.app"
BlueprintName = "LaunchAtLoginHelper"
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
BlueprintIdentifier = "BF58047A246A28F7008AE704"
BuildableName = "AltBackup.app"
BlueprintName = "AltBackup"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -26,20 +27,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E32E9B731EB87EA3000FEEE9"
BuildableName = "LaunchAtLoginHelper.app"
BlueprintName = "LaunchAtLoginHelper"
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -55,14 +44,42 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E32E9B731EB87EA3000FEEE9"
BuildableName = "LaunchAtLoginHelper.app"
BlueprintName = "LaunchAtLoginHelper"
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
BlueprintIdentifier = "BF58047A246A28F7008AE704"
BuildableName = "AltBackup.app"
BlueprintName = "AltBackup"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
<CommandLineArguments>
<CommandLineArgument
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.MigrationDebug 1"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.SQLiteIntegrityCheck 1"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.SQLDebug 1"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "OS_ACTIVITY_MODE"
value = "$(DEBUG_ACTIVITY_MODE)"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "OBJC_DEBUG_DUPLICATE_CLASSES"
value = "$(DEBUG_DUPLICATE_CLASSES)"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -74,10 +91,10 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E32E9B731EB87EA3000FEEE9"
BuildableName = "LaunchAtLoginHelper.app"
BlueprintName = "LaunchAtLoginHelper"
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
BlueprintIdentifier = "BF58047A246A28F7008AE704"
BuildableName = "AltBackup.app"
BlueprintName = "AltBackup"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>

View File

@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1030"
version = "1.3">
LastUpgradeVersion = "2620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
@@ -14,10 +15,10 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2DB6F1AF8C5EF6674B24A1A79C312CC2"
BuildableName = "Pods_AltStore.framework"
BlueprintName = "Pods-AltStore"
ReferencedContainer = "container:Pods.xcodeproj">
BlueprintIdentifier = "BF66EE7D2501AE50007EE018"
BuildableName = "AltStoreCore.framework"
BlueprintName = "AltStoreCore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -26,11 +27,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -42,17 +40,6 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2DB6F1AF8C5EF6674B24A1A79C312CC2"
BuildableName = "Pods_AltStore.framework"
BlueprintName = "Pods-AltStore"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -63,10 +50,10 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2DB6F1AF8C5EF6674B24A1A79C312CC2"
BuildableName = "Pods_AltStore.framework"
BlueprintName = "Pods-AltStore"
ReferencedContainer = "container:Pods.xcodeproj">
BlueprintIdentifier = "BF66EE7D2501AE50007EE018"
BuildableName = "AltStoreCore.framework"
BlueprintName = "AltStoreCore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>

View File

@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF989166250AABF3002ACF50"
BuildableName = "AltWidgetExtension.appex"
BlueprintName = "AltWidgetExtension"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "_XCWidgetKind"
value = ""
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetDefaultView"
value = "timeline"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetFamily"
value = "systemMedium"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:SideStore/Tests/DataStructureTests.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A81A8CC42D68BA610086C96F"
BuildableName = "DataStructureTests.xctest"
BlueprintName = "DataStructureTests"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
@@ -15,34 +15,22 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "AltStore.app"
BlueprintName = "AltStore"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "AltStore.app"
BlueprintName = "AltStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
@@ -56,19 +44,41 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "AltStore.app"
BlueprintName = "AltStore"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.MigrationDebug 1"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.SQLiteIntegrityCheck 1"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.SQLDebug 1"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
<AdditionalOptions>
</AdditionalOptions>
<EnvironmentVariables>
<EnvironmentVariable
key = "OS_ACTIVITY_MODE"
value = "$(DEBUG_ACTIVITY_MODE)"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "OBJC_DEBUG_DUPLICATE_CLASSES"
value = "$(DEBUG_DUPLICATE_CLASSES)"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -81,14 +91,14 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "AltStore.app"
BlueprintName = "AltStore"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
buildConfiguration = "Release">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"

View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:SideStore/Tests/SideStoreTests.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A8E2DB202D684CBD009E5D31"
BuildableName = "UITests.xctest"
BlueprintName = "UITests"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
<SelectedTests>
<Test
Identifier = "UITests/testExample()">
</Test>
</SelectedTests>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.MigrationDebug 1"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.SQLiteIntegrityCheck 1"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.SQLDebug 1"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "OS_ACTIVITY_MODE"
value = "$(DEBUG_ACTIVITY_MODE)"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "OBJC_DEBUG_DUPLICATE_CLASSES"
value = "$(DEBUG_DUPLICATE_CLASSES)"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0940"
version = "1.3">
LastUpgradeVersion = "2630"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
@@ -14,10 +15,10 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E32E9B621EB87D7B000FEEE9"
BuildableName = "LaunchAtLogin.framework"
BlueprintName = "LaunchAtLogin"
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
BlueprintIdentifier = "A85A51412F4B4532002E2E11"
BuildableName = "libem_proxy_swift.a"
BlueprintName = "em_proxy-swift"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -26,11 +27,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -42,17 +40,6 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E32E9B621EB87D7B000FEEE9"
BuildableName = "LaunchAtLogin.framework"
BlueprintName = "LaunchAtLogin"
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -63,10 +50,10 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E32E9B621EB87D7B000FEEE9"
BuildableName = "LaunchAtLogin.framework"
BlueprintName = "LaunchAtLogin"
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
BlueprintIdentifier = "A85A51412F4B4532002E2E11"
BuildableName = "libem_proxy_swift.a"
BlueprintName = "em_proxy-swift"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>

View File

@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
LastUpgradeVersion = "2620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
@@ -14,9 +15,9 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF45868C229872EA00BD7491"
BuildableName = "AltServer.app"
BlueprintName = "AltServer"
BlueprintIdentifier = "BF45872A2298D31600BD7491"
BuildableName = "libimobiledevice.a"
BlueprintName = "libimobiledevice"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@@ -26,20 +27,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF45868C229872EA00BD7491"
BuildableName = "AltServer.app"
BlueprintName = "AltServer"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -51,18 +40,6 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF45868C229872EA00BD7491"
BuildableName = "AltServer.app"
BlueprintName = "AltServer"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -70,16 +47,15 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF45868C229872EA00BD7491"
BuildableName = "AltServer.app"
BlueprintName = "AltServer"
BlueprintIdentifier = "BF45872A2298D31600BD7491"
BuildableName = "libimobiledevice.a"
BlueprintName = "libimobiledevice"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "container:AltStore.xcodeproj">
</FileRef>
<FileRef
location = "group:Dependencies/AltSign/AltSign.xcodeproj">
</FileRef>
<FileRef
location = "group:Dependencies/Roxas/Roxas.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -2,6 +2,4 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "NSError+ALTServerError.h"
#import "ALTAppPermission.h"
#import "ALTPatreonBenefitType.h"
#import "NSAttributedString+Markdown.h"

View File

@@ -4,5 +4,15 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
<true/>
<key>com.apple.developer.kernel.increased-debugging-memory-limit</key>
<true/>
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.$(APP_GROUP_IDENTIFIER)</string>
</array>
</dict>
</plist>

View File

@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.$(APP_GROUP_IDENTIFIER)</string>
</array>
</dict>
</plist>

View File

@@ -7,7 +7,7 @@
//
import UIKit
import AltStoreCore
import Roxas
import Nuke
@@ -24,13 +24,11 @@ extension AppContentViewController
}
}
class AppContentViewController: UITableViewController
final class AppContentViewController: UITableViewController
{
var app: StoreApp!
private lazy var screenshotsDataSource = self.makeScreenshotsDataSource()
private lazy var permissionsDataSource = self.makePermissionsDataSource()
// private lazy var screenshotsDataSource = self.makeScreenshotsDataSource()
private lazy var dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
@@ -44,135 +42,113 @@ class AppContentViewController: UITableViewController
}()
@IBOutlet private var subtitleLabel: UILabel!
@IBOutlet private var descriptionTextView: CollapsingTextView!
@IBOutlet private var versionDescriptionTextView: CollapsingTextView!
// @IBOutlet private var descriptionTextView: CollapsingTextView!
@IBOutlet private var descriptionTextView: CollapsingMarkdownView!
// @IBOutlet private var versionDescriptionTextView: CollapsingTextView!
@IBOutlet private var versionDescriptionTextView: CollapsingMarkdownView!
@IBOutlet private var versionLabel: UILabel!
@IBOutlet private var versionDateLabel: UILabel!
@IBOutlet private var sizeLabel: UILabel!
@IBOutlet private var screenshotsCollectionView: UICollectionView!
@IBOutlet private var permissionsCollectionView: UICollectionView!
@IBOutlet private(set) var appScreenshotsViewController: AppScreenshotsViewController!
@IBOutlet private var appScreenshotsHeightConstraint: NSLayoutConstraint!
var preferredScreenshotSize: CGSize? {
let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
let aspectRatio: CGFloat = 16.0 / 9.0 // Hardcoded for now.
let width = self.screenshotsCollectionView.bounds.width - (layout.minimumInteritemSpacing * 2)
let itemWidth = width / 1.5
let itemHeight = itemWidth * aspectRatio
return CGSize(width: itemWidth, height: itemHeight)
}
@IBOutlet private(set) var appDetailCollectionViewController: AppDetailCollectionViewController!
@IBOutlet private var appDetailCollectionViewHeightConstraint: NSLayoutConstraint!
override func viewDidLoad()
{
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.contentInset.bottom = 20
self.screenshotsCollectionView.dataSource = self.screenshotsDataSource
self.permissionsCollectionView.dataSource = self.permissionsDataSource
self.subtitleLabel.text = self.app.subtitle
self.descriptionTextView.text = self.app.localizedDescription
self.versionDescriptionTextView.text = self.app.versionDescription
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), self.app.version)
self.versionDateLabel.text = Date().relativeDateString(since: self.app.versionDate, dateFormatter: self.dateFormatter)
self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: Int64(self.app.size))
let desc = self.app.localizedDescription
self.descriptionTextView.text = desc
if let version = self.app.latestAvailableVersion {
self.versionDescriptionTextView.text = version.localizedDescription ?? "nil"
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), version.localizedVersion)
self.versionDateLabel.text = Date().relativeDateString(since: version.date)
self.sizeLabel.text = ByteCountFormatter.string(fromByteCount: version.size, countStyle: .file)
} else {
self.versionDescriptionTextView.text = "nil"
self.versionLabel.text = nil
self.versionDateLabel.text = nil
self.sizeLabel.text = ByteCountFormatter.string(fromByteCount: 0, countStyle: .file)
}
self.descriptionTextView.maximumNumberOfLines = 5
self.descriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
self.versionDescriptionTextView.maximumNumberOfLines = 5
self.versionDescriptionTextView.maximumNumberOfLines = 3
self.versionDescriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
self.descriptionTextView.toggleButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
self.versionDescriptionTextView.toggleButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
guard var size = self.preferredScreenshotSize else { return }
size.height = min(size.height, self.screenshotsCollectionView.bounds.height) // Silence temporary "item too tall" warning.
var needsTableViewUpdate = false
let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = size
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
guard segue.identifier == "showPermission" else { return }
let screenshotsHeight = self.appScreenshotsViewController.collectionView.contentSize.height
if self.appScreenshotsHeightConstraint.constant != screenshotsHeight && screenshotsHeight > 0
{
self.appScreenshotsHeightConstraint.constant = screenshotsHeight
needsTableViewUpdate = true
}
guard let cell = sender as? UICollectionViewCell, let indexPath = self.permissionsCollectionView.indexPath(for: cell) else { return }
let permissionsHeight = self.appDetailCollectionViewController.collectionView.contentSize.height
if self.appDetailCollectionViewHeightConstraint.constant != permissionsHeight && permissionsHeight > 0
{
self.appDetailCollectionViewHeightConstraint.constant = permissionsHeight
needsTableViewUpdate = true
}
let permission = self.permissionsDataSource.item(at: indexPath)
let maximumWidth = self.view.bounds.width - 20
let permissionPopoverViewController = segue.destination as! PermissionPopoverViewController
permissionPopoverViewController.permission = permission
permissionPopoverViewController.view.widthAnchor.constraint(lessThanOrEqualToConstant: maximumWidth).isActive = true
let size = permissionPopoverViewController.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
permissionPopoverViewController.preferredContentSize = size
permissionPopoverViewController.popoverPresentationController?.delegate = self
permissionPopoverViewController.popoverPresentationController?.sourceRect = cell.frame
permissionPopoverViewController.popoverPresentationController?.sourceView = self.permissionsCollectionView
if needsTableViewUpdate
{
UIView.performWithoutAnimation {
// Update row height without animation.
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
}
}
}
private extension AppContentViewController
{
func makeScreenshotsDataSource() -> RSTArrayCollectionViewPrefetchingDataSource<NSURL, UIImage>
@IBSegueAction
func makeAppScreenshotsViewController(_ coder: NSCoder, sender: Any?) -> UIViewController?
{
let dataSource = RSTArrayCollectionViewPrefetchingDataSource<NSURL, UIImage>(items: self.app.screenshotURLs as [NSURL])
dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in
let cell = cell as! ScreenshotCollectionViewCell
cell.imageView.image = nil
cell.imageView.isIndicatingActivity = true
}
dataSource.prefetchHandler = { (imageURL, indexPath, completionHandler) in
return RSTAsyncBlockOperation() { (operation) in
ImagePipeline.shared.loadImage(with: imageURL as URL, progress: nil, completion: { (response, error) in
guard !operation.isCancelled else { return operation.finish() }
if let image = response?.image
{
completionHandler(image, nil)
}
else
{
completionHandler(nil, error)
}
})
}
}
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
let cell = cell as! ScreenshotCollectionViewCell
cell.imageView.isIndicatingActivity = false
cell.imageView.image = image
let appScreenshotsViewController = AppScreenshotsViewController(app: self.app, coder: coder)
self.appScreenshotsViewController = appScreenshotsViewController
return appScreenshotsViewController
}
func makePermissionsDataSource() -> RSTArrayCollectionViewDataSource<AppPermission>
{
let dataSource = RSTArrayCollectionViewDataSource(items: Array(self.app.permissions))
dataSource.cellConfigurationHandler = { (cell, permission, indexPath) in
let cell = cell as! PermissionCollectionViewCell
// cell.button.setImage(permission.type.icon, for: .normal)
// cell.button.tintColor = .label
// cell.textLabel.text = permission.type.localizedShortName ?? permission.type.localizedName
if let error = error
{
print("Error loading image:", error)
}
let icon = UIImage(systemName: permission.symbolName ?? "lock")
cell.button.setImage(icon, for: .normal)
cell.textLabel.text = permission.localizedDisplayName
}
return dataSource
}
func makePermissionsDataSource() -> RSTArrayCollectionViewDataSource<AppPermission>
{
let dataSource = RSTArrayCollectionViewDataSource(items: self.app.permissions)
dataSource.cellConfigurationHandler = { (cell, permission, indexPath) in
let cell = cell as! PermissionCollectionViewCell
cell.button.setImage(permission.type.icon, for: .normal)
cell.textLabel.text = permission.type.localizedShortName
}
return dataSource
@IBSegueAction
func makeAppDetailCollectionViewController(_ coder: NSCoder, sender: Any?) -> UIViewController?
{
let appDetailViewController = AppDetailCollectionViewController(app: self.app, coder: coder)
self.appDetailCollectionViewController = appDetailViewController
return appDetailViewController
}
}
@@ -184,8 +160,12 @@ private extension AppContentViewController
switch sender
{
case self.descriptionTextView.moreButton: indexPath = IndexPath(row: Row.description.rawValue, section: 0)
case self.versionDescriptionTextView.moreButton: indexPath = IndexPath(row: Row.versionDescription.rawValue, section: 0)
case self.descriptionTextView.toggleButton:
indexPath = IndexPath(row: Row.description.rawValue, section: 0)
case self.versionDescriptionTextView.toggleButton:
indexPath = IndexPath(row: Row.versionDescription.rawValue, section: 0)
default: return
}
@@ -205,17 +185,18 @@ extension AppContentViewController
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
guard indexPath.row == Row.screenshots.rawValue else { return super.tableView(tableView, heightForRowAt: indexPath) }
guard let size = self.preferredScreenshotSize else { return 0.0 }
return size.height
}
}
extension AppContentViewController: UIPopoverPresentationControllerDelegate
{
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle
{
return .none
switch Row.allCases[indexPath.row]
{
case .screenshots:
guard !self.app.allScreenshots.isEmpty else { return 0.0 }
return UITableView.automaticDimension
case .permissions:
guard !self.app.permissions.isEmpty else { return 0.0 }
return UITableView.automaticDimension
default:
return super.tableView(tableView, heightForRowAt: indexPath)
}
}
}

View File

@@ -8,7 +8,7 @@
import UIKit
class PermissionCollectionViewCell: UICollectionViewCell
final class PermissionCollectionViewCell: UICollectionViewCell
{
@IBOutlet var button: UIButton!
@IBOutlet var textLabel: UILabel!
@@ -29,7 +29,7 @@ class PermissionCollectionViewCell: UICollectionViewCell
}
}
class AppContentTableViewCell: UITableViewCell
final class AppContentTableViewCell: UITableViewCell
{
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize
{

View File

@@ -0,0 +1,299 @@
//
// AppDetailCollectionViewController.swift
// AltStore
//
// Created by Riley Testut on 5/5/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import UIKit
import SwiftUI
import AltStoreCore
import Roxas
extension AppDetailCollectionViewController
{
private enum Section: Int
{
case privacy
case knownEntitlements
case unknownEntitlements
}
private enum ElementKind: String
{
case title
case button
}
@objc(SafeAreaIgnoringCollectionView)
private class SafeAreaIgnoringCollectionView: UICollectionView
{
override var safeAreaInsets: UIEdgeInsets {
get {
// Fixes incorrect layout if collection view height is taller than safe area height.
return .zero
}
set {
// There MUST be a setter for this to work, even if it does nothing ¯\_()_/¯
}
}
}
}
class AppDetailCollectionViewController: UICollectionViewController
{
let app: StoreApp
private let privacyPermissions: [AppPermission]
private let knownEntitlementPermissions: [AppPermission]
private let unknownEntitlementPermissions: [AppPermission]
private lazy var dataSource = self.makeDataSource()
private lazy var privacyDataSource = self.makePrivacyDataSource()
private lazy var entitlementsDataSource = self.makeEntitlementsDataSource()
private var headerRegistration: UICollectionView.SupplementaryRegistration<UICollectionViewListCell>!
override var collectionViewLayout: UICollectionViewCompositionalLayout {
return self.collectionView.collectionViewLayout as! UICollectionViewCompositionalLayout
}
init?(app: StoreApp, coder: NSCoder)
{
self.app = app
let comparator: (AppPermission, AppPermission) -> Bool = { (permissionA, permissionB) -> Bool in
switch (permissionA.localizedName, permissionB.localizedName)
{
case (let nameA?, let nameB?):
// Sort by localizedName, if both have one.
return nameA.localizedStandardCompare(nameB) == .orderedAscending
case (nil, nil):
// Sort by raw permission value as fallback.
return permissionA.permission.rawValue < permissionB.permission.rawValue
// Sort "known" permissions before "unknown" ones.
case (_?, nil): return true
case (nil, _?): return false
}
}
self.privacyPermissions = app.permissions.filter { $0.type == .privacy }.sorted(by: comparator)
let entitlementPermissions = app.permissions.lazy.filter { $0.type == .entitlement }
self.knownEntitlementPermissions = entitlementPermissions.filter { $0.isKnown }.sorted(by: comparator)
self.unknownEntitlementPermissions = entitlementPermissions.filter { !$0.isKnown }.sorted(by: comparator)
super.init(coder: coder)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad()
{
super.viewDidLoad()
// Allow parent background color to show through.
self.collectionView.backgroundColor = nil
// Match the parent table view margins.
self.collectionView.directionalLayoutMargins.leading = 20
self.collectionView.directionalLayoutMargins.trailing = 20
let collectionViewLayout = self.makeLayout()
self.collectionView.collectionViewLayout = collectionViewLayout
self.collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "PrivacyCell")
self.collectionView.register(UICollectionViewListCell.self, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier)
self.headerRegistration = UICollectionView.SupplementaryRegistration<UICollectionViewListCell>(elementKind: UICollectionView.elementKindSectionHeader) { [weak self] (headerView, elementKind, indexPath) in
var configuration = UIListContentConfiguration.plainHeader()
// Match parent table view section headers.
configuration.textProperties.font = UIFont.systemFont(ofSize: 22, weight: .bold) // .boldSystemFont(ofSize:) returns *semi-bold* color smh.
configuration.textProperties.color = .label
switch Section(rawValue: indexPath.section)!
{
case .privacy: break
case .knownEntitlements:
configuration.text = nil
configuration.secondaryTextProperties.font = UIFont.preferredFont(forTextStyle: .callout)
configuration.textToSecondaryTextVerticalPadding = 8
configuration.secondaryText = NSLocalizedString("Entitlements are additional permissions that grant access to certain system services, including potentially sensitive information.", comment: "")
case .unknownEntitlements:
configuration.text = NSLocalizedString("Other Entitlements", comment: "")
let action = UIAction(image: UIImage(systemName: "questionmark.circle")) { _ in
self?.showUnknownEntitlementsAlert()
}
let helpButton = UIButton(primaryAction: action)
let customAccessory = UICellAccessory.customView(configuration: .init(customView: helpButton, placement: .trailing(), tintColor: self?.app.tintColor ?? .altPrimary))
headerView.accessories = [customAccessory]
}
headerView.contentConfiguration = configuration
headerView.backgroundConfiguration = UIBackgroundConfiguration.clear()
}
self.dataSource.proxy = self
self.collectionView.dataSource = self.dataSource
self.collectionView.delegate = self
}
}
private extension AppDetailCollectionViewController
{
func makeLayout() -> UICollectionViewCompositionalLayout
{
let layoutConfig = UICollectionViewCompositionalLayoutConfiguration()
layoutConfig.contentInsetsReference = .layoutMargins
let layout = UICollectionViewCompositionalLayout(sectionProvider: { [privacyPermissions, knownEntitlementPermissions, unknownEntitlementPermissions] (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
guard let section = Section(rawValue: sectionIndex) else { return nil }
switch section
{
case .privacy:
guard !privacyPermissions.isEmpty, #available(iOS 16, *) else { return nil } // Hide section pre-iOS 16.
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(50)) // Underestimate height to prevent jumping size abruptly.
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(50))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let layoutSection = NSCollectionLayoutSection(group: group)
layoutSection.interGroupSpacing = 10
return layoutSection
case .knownEntitlements where !knownEntitlementPermissions.isEmpty: fallthrough
case .unknownEntitlements where !unknownEntitlementPermissions.isEmpty:
var configuration = UICollectionLayoutListConfiguration(appearance: .plain)
configuration.headerMode = .supplementary
configuration.showsSeparators = false
configuration.backgroundColor = .altBackground
let layoutSection = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
layoutSection.contentInsets.top = 4
return layoutSection
case .knownEntitlements, .unknownEntitlements: return nil
}
}, configuration: layoutConfig)
return layout
}
func makeDataSource() -> RSTCompositeCollectionViewDataSource<AppPermission>
{
let dataSource = RSTCompositeCollectionViewDataSource(dataSources: [self.privacyDataSource, self.entitlementsDataSource])
return dataSource
}
func makePrivacyDataSource() -> RSTDynamicCollectionViewDataSource<AppPermission>
{
let dataSource = RSTDynamicCollectionViewDataSource<AppPermission>()
dataSource.cellIdentifierHandler = { _ in "PrivacyCell" }
dataSource.numberOfSectionsHandler = { 1 }
dataSource.cellConfigurationHandler = { [weak self] (cell, _, indexPath) in
guard let self, #available(iOS 16, *) else { return }
cell.contentConfiguration = UIHostingConfiguration {
AppPermissionsCard(title: "Privacy",
description: "\(self.app.name) may request access to the following:",
tintColor: Color(uiColor: self.app.tintColor ?? .altPrimary),
permissions: self.privacyPermissions)
}
.margins(.horizontal, 0)
}
if #available(iOS 16, *)
{
dataSource.numberOfItemsHandler = { [privacyPermissions] _ in !privacyPermissions.isEmpty ? 1 : 0 }
}
else
{
dataSource.numberOfItemsHandler = { _ in 0 }
}
return dataSource
}
func makeEntitlementsDataSource() -> RSTCompositeCollectionViewDataSource<AppPermission>
{
let knownEntitlementsDataSource = RSTArrayCollectionViewDataSource(items: self.knownEntitlementPermissions)
let unknownEntitlementsDataSource = RSTArrayCollectionViewDataSource(items: self.unknownEntitlementPermissions)
let dataSource = RSTCompositeCollectionViewDataSource(dataSources: [knownEntitlementsDataSource, unknownEntitlementsDataSource])
dataSource.cellConfigurationHandler = { [weak self] (cell, appPermission, _) in
let cell = cell as! UICollectionViewListCell
let tintColor = self?.app.tintColor ?? .altPrimary
var content = cell.defaultContentConfiguration()
content.text = appPermission.localizedDisplayName
content.secondaryText = appPermission.permission.rawValue
content.secondaryTextProperties.color = .secondaryLabel
if appPermission.isKnown
{
content.image = UIImage(systemName: appPermission.effectiveSymbolName)
content.imageProperties.tintColor = tintColor
if #available(iOS 15.4, *) /*, let self */ // Capturing self leads to strong-reference cycle.
{
let detailAccessory = UICellAccessory.detail(options: .init(tintColor: tintColor)) {
self?.showPermissionAlert(for: appPermission)
}
cell.accessories = [detailAccessory]
}
}
cell.contentConfiguration = content
cell.backgroundConfiguration = UIBackgroundConfiguration.clear()
}
return dataSource
}
}
private extension AppDetailCollectionViewController
{
func showPermissionAlert(for permission: AppPermission)
{
let alertController = UIAlertController(title: permission.localizedDisplayName, message: permission.localizedDescription, preferredStyle: .alert)
alertController.addAction(.ok)
self.present(alertController, animated: true)
}
func showUnknownEntitlementsAlert()
{
let alertController = UIAlertController(title: NSLocalizedString("Other Entitlements", comment: ""), message: NSLocalizedString("SideStore does not have detailed information for these entitlements.", comment: ""), preferredStyle: .alert)
alertController.addAction(.ok)
self.present(alertController, animated: true)
}
}
extension AppDetailCollectionViewController
{
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
{
let headerView = self.collectionView.dequeueConfiguredReusableSupplementary(using: self.headerRegistration, for: indexPath)
return headerView
}
override func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool
{
return false
}
override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool
{
return false
}
}

View File

@@ -0,0 +1,275 @@
//
// AppPermissionsCard.swift
// AltStore
//
// Created by Riley Testut on 5/4/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import SwiftUI
import AltStoreCore
@available(iOS 16, *)
extension AppPermissionsCard
{
private struct TransitionKey: Hashable
{
static func name(_ permission: Permission) -> TransitionKey {
TransitionKey(key: "name", permission: permission)
}
static func icon(_ permission: Permission) -> TransitionKey {
TransitionKey(key: "icon", permission: permission)
}
let key: String
let permission: Permission
private init(key: String, permission: Permission)
{
self.key = key
self.permission = permission
}
}
}
@available(iOS 16, *)
struct AppPermissionsCard<Permission: AppPermissionProtocol>: View
{
let title: LocalizedStringKey
let description: LocalizedStringKey
let tintColor: Color
let permissions: [Permission]
@State
private var selectedPermission: Permission?
@Namespace
private var animation
private var isTitleVisible: Bool {
if selectedPermission == nil
{
// Title should always be visible when showing all permissions.
return true
}
// If showing permission details, only show title if there
// are more than 2 permissions total to save vertical space.
let isTitleVisible = permissions.count > 2
return isTitleVisible
}
var body: some View {
let title = Text(title)
.font(.title3)
.bold()
.minimumScaleFactor(0.1) // Avoid clipping during matchedGeometryEffect animation.
VStack(spacing: 8) {
if isTitleVisible
{
// If title is visible, place _outside_ `content`
// to avoid being covered by permissionDetailView.
title
}
let content = VStack(spacing: 8) {
if !isTitleVisible
{
// Place title inside `content` when not visible
// so it's covered by permissionDetailView.
title
}
VStack(spacing: 20) {
Text(description)
.font(.subheadline)
.fixedSize(horizontal: false, vertical: true)
Grid(verticalSpacing: 15) {
ForEach(permissions, id: \.self) { permission in
permissionRow(for: permission)
}
}
Text("Tap a permission to learn more.")
.font(.subheadline)
.fixedSize(horizontal: false, vertical: true)
}
}
if let selectedPermission
{
// Hide content with overlay to preserve existing size.
content.hidden().overlay {
permissionDetailView(for: selectedPermission)
}
}
else
{
content
}
}
.overlay(alignment: .topTrailing) {
if selectedPermission != nil
{
Image(systemName: "xmark.circle.fill")
.imageScale(.medium)
}
}
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
.padding(20)
.overlay {
if selectedPermission != nil
{
// Make entire view tappable when overlay is visible.
SwiftUI.Button(action: hidePermission) {
VStack {}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
.foregroundColor(.secondary) // Vibrancy
.background(.regularMaterial) // Blur background for auto-legibility correction.
.background(tintColor, in: RoundedRectangle(cornerRadius: 30, style: .continuous))
}
@ViewBuilder
private func permissionRow(for permission: Permission) -> some View
{
GridRow {
SwiftUI.Button(action: { show(permission) }) {
HStack {
let text = Text(permission.localizedDisplayName)
.font(.body)
.bold()
.minimumScaleFactor(0.33)
.lineLimit(.max) // Setting lineLimit to anything fixes text wrapping at large text sizes.
let image = Image(systemName: permission.effectiveSymbolName)
.gridColumnAlignment(.center)
if selectedPermission != nil
{
Label(title: { text }, icon: { image })
.hidden()
}
else
{
Label {
text.matchedGeometryEffect(id: TransitionKey.name(permission), in: animation)
} icon: {
image.matchedGeometryEffect(id: TransitionKey.icon(permission), in: animation)
}
}
Spacer()
Image(systemName: "info.circle")
.imageScale(.large)
}
.contentShape(Rectangle()) // Make entire HStack tappable.
}
}
.frame(minHeight: 30) // Make row tall enough to tap.
}
@ViewBuilder
private func permissionDetailView(for permission: Permission) -> some View
{
VStack(spacing: 15) {
Image(systemName: permission.effectiveSymbolName)
.font(.largeTitle)
.fixedSize(horizontal: false, vertical: true)
.matchedGeometryEffect(id: TransitionKey.icon(permission), in: animation)
Text(permission.localizedDisplayName)
.font(.title2)
.bold()
.minimumScaleFactor(0.1) // Avoid clipping during matchedGeometryEffect animation.
.matchedGeometryEffect(id: TransitionKey.name(permission), in: animation)
if let usageDescription = permission.usageDescription
{
Text(usageDescription)
.font(.subheadline)
.minimumScaleFactor(0.75)
}
}
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
init(title: LocalizedStringKey, description: LocalizedStringKey, tintColor: Color, permissions: [Permission])
{
self.init(title: title, description: description, tintColor: tintColor, permissions: permissions, selectedPermission: nil)
}
fileprivate init(title: LocalizedStringKey, description: LocalizedStringKey, tintColor: Color, permissions: [Permission], selectedPermission: Permission? = nil)
{
self.title = title
self.description = description
self.tintColor = tintColor
self.permissions = permissions
// Set _selectedPermission directly or else the preview won't detect it.
self._selectedPermission = State(initialValue: selectedPermission)
}
}
@available(iOS 16, *)
private extension AppPermissionsCard
{
func show(_ permission: Permission)
{
withAnimation {
self.selectedPermission = permission
}
}
func hidePermission()
{
withAnimation {
self.selectedPermission = nil
}
}
}
@available(iOS 16, *)
struct AppPermissionsCard_Previews: PreviewProvider
{
static var previews: some View {
let appPermissions = [
PreviewAppPermission(permission: ALTAppPrivacyPermission.localNetwork),
PreviewAppPermission(permission: ALTAppPrivacyPermission.microphone),
PreviewAppPermission(permission: ALTAppPrivacyPermission.photos),
PreviewAppPermission(permission: ALTAppPrivacyPermission.camera),
PreviewAppPermission(permission: ALTAppPrivacyPermission.faceID),
PreviewAppPermission(permission: ALTAppPrivacyPermission.appleMusic),
PreviewAppPermission(permission: ALTAppPrivacyPermission.bluetooth),
PreviewAppPermission(permission: ALTAppPrivacyPermission.calendars),
]
let tintColor = Color(uiColor: .deltaPrimary!)
return ForEach(1...8, id: \.self) { index in
AppPermissionsCard(title: "Privacy",
description: "Delta may request access to the following:",
tintColor: tintColor,
permissions: Array(appPermissions.prefix(index)))
.frame(width: 350)
.previewLayout(.sizeThatFits)
AppPermissionsCard(title: "Privacy",
description: "Delta may request access to the following:",
tintColor: tintColor,
permissions: Array(appPermissions.prefix(index)),
selectedPermission: appPermissions.first)
.frame(width: 350)
.previewLayout(.sizeThatFits)
}
}
}

View File

@@ -7,12 +7,12 @@
//
import UIKit
import AltStoreCore
import Roxas
import Nuke
class AppViewController: UIViewController
final class AppViewController: UIViewController
{
var app: StoreApp!
@@ -27,18 +27,11 @@ class AppViewController: UIViewController
@IBOutlet private var scrollView: UIScrollView!
@IBOutlet private var contentView: UIView!
@IBOutlet private var headerView: UIView!
@IBOutlet private var headerContentView: UIView!
@IBOutlet private var bannerView: AppBannerView!
@IBOutlet private var backButton: UIButton!
@IBOutlet private var backButtonContainerView: UIVisualEffectView!
@IBOutlet private var nameLabel: UILabel!
@IBOutlet private var developerLabel: UILabel!
@IBOutlet private var downloadButton: PillButton!
@IBOutlet private var appIconImageView: UIImageView!
@IBOutlet private var betaBadgeView: UIImageView!
@IBOutlet private var backgroundAppIconImageView: UIImageView!
@IBOutlet private var backgroundBlurView: UIVisualEffectView!
@@ -48,9 +41,24 @@ class AppViewController: UIViewController
@IBOutlet private var navigationBarAppNameLabel: UILabel!
private var _shouldResetLayout = false
private var _viewDidAppear = false
private var _backgroundBlurEffect: UIBlurEffect?
private var _backgroundBlurTintColor: UIColor?
private var _preferredStatusBarStyle: UIStatusBarStyle = .default
override var preferredStatusBarStyle: UIStatusBarStyle {
if #available(iOS 17, *)
{
// On iOS 17+, .default will update the status bar automatically.
return .default
}
else
{
return _preferredStatusBarStyle
}
}
override func viewDidLoad()
{
super.viewDidLoad()
@@ -58,6 +66,11 @@ class AppViewController: UIViewController
self.navigationBarTitleView.sizeToFit()
self.navigationItem.titleView = self.navigationBarTitleView
// spacing in storyboard wasn't working, so had to do programatically
if let stackView = self.navigationBarTitleView as? UIStackView {
stackView.spacing = 8
}
self.contentViewControllerShadowView = UIView()
self.contentViewControllerShadowView.backgroundColor = .white
self.contentViewControllerShadowView.layer.cornerRadius = 38
@@ -73,27 +86,26 @@ class AppViewController: UIViewController
self.contentViewController.view.layer.masksToBounds = true
self.contentViewController.tableView.panGestureRecognizer.require(toFail: self.scrollView.panGestureRecognizer)
self.contentViewController.appDetailCollectionViewController.collectionView.panGestureRecognizer.require(toFail: self.scrollView.panGestureRecognizer)
self.contentViewController.tableView.showsVerticalScrollIndicator = false
self.headerView.frame = CGRect(x: 0, y: 0, width: 300, height: 93)
self.headerView.layer.cornerRadius = 24
self.headerView.layer.masksToBounds = true
// Bring to front so the scroll indicators are visible.
self.view.bringSubviewToFront(self.scrollView)
self.scrollView.isUserInteractionEnabled = false
self.nameLabel.text = self.app.name
self.developerLabel.text = self.app.developerName
self.developerLabel.textColor = self.app.tintColor
self.appIconImageView.image = nil
self.appIconImageView.tintColor = self.app.tintColor
self.downloadButton.tintColor = self.app.tintColor
self.betaBadgeView.isHidden = !self.app.isBeta
self.bannerView.frame = CGRect(x: 0, y: 0, width: 300, height: 93)
self.bannerView.backgroundEffectView.effect = UIBlurEffect(style: .regular)
self.bannerView.backgroundEffectView.backgroundColor = .clear
self.bannerView.iconImageView.image = nil
self.bannerView.iconImageView.tintColor = self.app.tintColor
self.bannerView.button.tintColor = self.app.tintColor
self.bannerView.tintColor = self.app.tintColor
self.bannerView.accessibilityTraits.remove(.button)
self.bannerView.button.addTarget(self, action: #selector(AppViewController.performAppAction(_:)), for: .primaryActionTriggered)
self.backButtonContainerView.tintColor = self.app.tintColor
self.navigationController?.navigationBar.tintColor = self.app.tintColor
self.navigationBarDownloadButton.tintColor = self.app.tintColor
self.navigationBarAppNameLabel.text = self.app.name
self.navigationBarAppIconImageView.tintColor = self.app.tintColor
@@ -107,22 +119,27 @@ class AppViewController: UIViewController
NotificationCenter.default.addObserver(self, selector: #selector(AppViewController.didChangeApp(_:)), name: .NSManagedObjectContextObjectsDidChange, object: DatabaseManager.shared.viewContext)
NotificationCenter.default.addObserver(self, selector: #selector(AppViewController.willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(AppViewController.didBecomeActive(_:)), name: UIApplication.didBecomeActiveNotification, object: nil)
self._backgroundBlurEffect = self.backgroundBlurView.effect as? UIBlurEffect
self._backgroundBlurTintColor = self.backgroundBlurView.contentView.backgroundColor
// Load Images
for imageView in [self.appIconImageView!, self.backgroundAppIconImageView!, self.navigationBarAppIconImageView!]
for imageView in [self.bannerView.iconImageView!, self.backgroundAppIconImageView!, self.navigationBarAppIconImageView!]
{
imageView.isIndicatingActivity = true
Nuke.loadImage(with: self.app.iconURL, options: .shared, into: imageView, progress: nil) { [weak imageView] (response, error) in
if response?.image != nil
Nuke.loadImage(with: self.app.iconURL, options: .shared, into: imageView, progress: nil) { [weak imageView] (result) in
switch result
{
imageView?.isIndicatingActivity = false
case .success: imageView?.isIndicatingActivity = false
case .failure(let error): print("[ALTLog] Failed to load app icons.", error)
}
}
}
// Start with navigation bar hidden.
self.hideNavigationBar()
}
override func viewWillAppear(_ animated: Bool)
@@ -134,42 +151,26 @@ class AppViewController: UIViewController
// Update blur immediately.
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
self.transitionCoordinator?.animate(alongsideTransition: { (context) in
self.hideNavigationBar()
}, completion: nil)
}
override func viewIsAppearing(_ animated: Bool)
{
super.viewIsAppearing(animated)
// Prevent banner temporarily flashing a color due to being added back to self.view.
self.bannerView.backgroundEffectView.backgroundColor = .clear
}
override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)
self._viewDidAppear = true
self._shouldResetLayout = true
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
override func viewWillDisappear(_ animated: Bool)
{
super.viewWillDisappear(animated)
// Guard against "dismissing" when presenting via 3D Touch pop.
guard self.navigationController != nil else { return }
// Store reference since self.navigationController will be nil after disappearing.
let navigationController = self.navigationController
navigationController?.navigationBar.barStyle = .default // Don't animate, or else status bar might appear messed-up.
self.transitionCoordinator?.animate(alongsideTransition: { (context) in
self.showNavigationBar(for: navigationController)
}, completion: { (context) in
if !context.isCancelled
{
self.showNavigationBar(for: navigationController)
}
})
}
override func viewDidDisappear(_ animated: Bool)
{
super.viewDidDisappear(animated)
@@ -186,6 +187,12 @@ class AppViewController: UIViewController
self.contentViewController = segue.destination as? AppContentViewController
self.contentViewController.app = self.app
if #available(iOS 15, *)
{
// Fix navigation bar + tab bar appearance on iOS 15.
self.setContentScrollView(self.scrollView)
}
}
override func viewDidLayoutSubviews()
@@ -196,11 +203,6 @@ class AppViewController: UIViewController
{
// Various events can cause UI to mess up, so reset affected components now.
if self.navigationController?.topViewController == self
{
self.hideNavigationBar()
}
self.prepareBlur()
// Reset navigation bar animation, and create a new one later in this method if necessary.
@@ -208,8 +210,22 @@ class AppViewController: UIViewController
self._shouldResetLayout = false
}
let statusBarHeight = UIApplication.shared.statusBarFrame.height
let statusBarHeight: Double
if let navigationController, navigationController.presentingViewController != nil, navigationController.modalPresentationStyle != .fullScreen
{
statusBarHeight = 20
}
else if let statusBarManager = (self.view.window ?? self.presentedViewController?.view.window)?.windowScene?.statusBarManager
{
statusBarHeight = statusBarManager.statusBarFrame.height
}
else
{
statusBarHeight = 0
}
let cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
let inset = 12 as CGFloat
@@ -219,7 +235,7 @@ class AppViewController: UIViewController
var backButtonFrame = CGRect(x: inset, y: statusBarHeight,
width: backButtonSize.width + 20, height: backButtonSize.height + 20)
var headerFrame = CGRect(x: inset, y: 0, width: self.view.bounds.width - inset * 2, height: self.headerView.bounds.height)
var headerFrame = CGRect(x: inset, y: 0, width: self.view.bounds.width - inset * 2, height: self.bannerView.bounds.height)
var contentFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height)
var backgroundIconFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.width)
@@ -268,13 +284,25 @@ class AppViewController: UIViewController
}
let difference = self.scrollView.contentOffset.y - showNavigationBarThreshold
let range = (headerFrame.height + padding) - (self.navigationController?.navigationBar.bounds.height ?? self.view.safeAreaInsets.top)
let range: Double
if self.presentingViewController == nil && self.parent?.presentingViewController == nil
{
// Not presented modally, so rely on safe area + navigation bar height.
range = (headerFrame.height + padding) - (self.navigationController?.navigationBar.bounds.height ?? self.view.safeAreaInsets.top)
}
else
{
// Presented modally, so rely on maximumContentY.
range = maximumContentY - (maximumContentY - padding - headerFrame.height) - inset
}
let fractionComplete = min(difference, range) / range
self.navigationBarAnimator?.fractionComplete = fractionComplete
}
else
{
self.navigationBarAnimator?.fractionComplete = 0.0
self.resetNavigationBarAnimation()
}
@@ -305,17 +333,16 @@ class AppViewController: UIViewController
// Set frames.
self.contentViewController.view.superview?.frame = contentFrame
self.headerView.frame = headerFrame
self.bannerView.frame = headerFrame
self.backgroundAppIconImageView.frame = backgroundIconFrame
self.backgroundBlurView.frame = backgroundIconFrame
self.backButtonContainerView.frame = backButtonFrame
self.headerContentView.frame = CGRect(x: 0, y: 0, width: self.headerView.bounds.width, height: self.headerView.bounds.height)
self.contentViewControllerShadowView.frame = self.contentViewController.view.frame
self.backButtonContainerView.layer.cornerRadius = self.backButtonContainerView.bounds.midY
self.scrollView.scrollIndicatorInsets.top = statusBarHeight
self.scrollView.verticalScrollIndicatorInsets.top = statusBarHeight
// Adjust content offset + size.
let contentOffset = self.scrollView.contentOffset
@@ -325,6 +352,18 @@ class AppViewController: UIViewController
self.scrollView.contentSize = contentSize
self.scrollView.contentOffset = contentOffset
self.bannerView.backgroundEffectView.backgroundColor = .clear
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
{
super.traitCollectionDidChange(previousTraitCollection)
if self._viewDidAppear
{
self._shouldResetLayout = true
}
}
deinit
@@ -336,7 +375,7 @@ class AppViewController: UIViewController
extension AppViewController
{
class func makeAppViewController(app: StoreApp) -> AppViewController
final class func makeAppViewController(app: StoreApp) -> AppViewController
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
@@ -350,57 +389,94 @@ private extension AppViewController
{
func update()
{
for button in [self.downloadButton!, self.navigationBarDownloadButton!]
var buttonAction: AppBannerView.AppAction?
if let installedApp = self.app.installedApp, installedApp.hasUpdate
{
// Explicitly set button action to .update if there is an update available, even if it's not supported.
buttonAction = .update
}
for button in [self.bannerView.button!, self.navigationBarDownloadButton!]
{
button.tintColor = self.app.tintColor
button.isIndicatingActivity = false
if self.app.installedApp == nil
{
button.setTitle(NSLocalizedString("FREE", comment: ""), for: .normal)
button.isInverted = false
}
else
{
button.setTitle(NSLocalizedString("OPEN", comment: ""), for: .normal)
button.isInverted = true
}
let progress = AppManager.shared.installationProgress(for: self.app)
button.progress = progress
}
if Date() < self.app.versionDate
{
self.downloadButton.countdownDate = self.app.versionDate
self.navigationBarDownloadButton.countdownDate = self.app.versionDate
}
else
{
self.downloadButton.countdownDate = nil
self.navigationBarDownloadButton.countdownDate = nil
}
self.bannerView.configure(for: self.app, action: buttonAction)
let title = self.bannerView.button.title(for: .normal)
self.navigationBarDownloadButton.setTitle(title, for: .normal)
self.navigationBarDownloadButton.progress = self.bannerView.button.progress
self.navigationBarDownloadButton.countdownDate = self.bannerView.button.countdownDate
let barButtonItem = self.navigationItem.rightBarButtonItem
self.navigationItem.rightBarButtonItem = nil
self.navigationItem.rightBarButtonItem = barButtonItem
}
func showNavigationBar(for navigationController: UINavigationController? = nil)
func showNavigationBar()
{
let navigationController = navigationController ?? self.navigationController
navigationController?.navigationBar.barStyle = .default
navigationController?.navigationBar.alpha = 1.0
navigationController?.navigationBar.barTintColor = .white
navigationController?.navigationBar.tintColor = .altPrimary
self.navigationBarAppIconImageView.alpha = 1.0
self.navigationBarAppNameLabel.alpha = 1.0
self.navigationBarDownloadButton.alpha = 1.0
self.updateNavigationBarAppearance(isHidden: false)
if self.traitCollection.userInterfaceStyle == .dark
{
self._preferredStatusBarStyle = .lightContent
}
else
{
self._preferredStatusBarStyle = .default
}
if #unavailable(iOS 17)
{
self.navigationController?.setNeedsStatusBarAppearanceUpdate()
}
}
func hideNavigationBar(for navigationController: UINavigationController? = nil)
func hideNavigationBar()
{
let navigationController = navigationController ?? self.navigationController
navigationController?.navigationBar.barStyle = .black
navigationController?.navigationBar.alpha = 0.0
navigationController?.navigationBar.barTintColor = .white
self.navigationBarAppIconImageView.alpha = 0.0
self.navigationBarAppNameLabel.alpha = 0.0
self.navigationBarDownloadButton.alpha = 0.0
self.updateNavigationBarAppearance(isHidden: true)
self._preferredStatusBarStyle = .lightContent
if #unavailable(iOS 17)
{
self.navigationController?.setNeedsStatusBarAppearanceUpdate()
}
}
// Copied from HeaderContentViewController
func updateNavigationBarAppearance(isHidden: Bool)
{
let barAppearance = self.navigationItem.standardAppearance as? NavigationBarAppearance ?? NavigationBarAppearance()
if isHidden
{
barAppearance.configureWithTransparentBackground()
barAppearance.ignoresUserInteraction = true
}
else
{
barAppearance.configureWithDefaultBackground()
barAppearance.ignoresUserInteraction = false
}
barAppearance.titleTextAttributes = [.foregroundColor: UIColor.clear]
let tintColor = isHidden ? UIColor.clear : self.app.tintColor ?? .altPrimary
barAppearance.configureWithTintColor(tintColor)
self.navigationItem.standardAppearance = barAppearance
self.navigationItem.scrollEdgeAppearance = barAppearance
}
func prepareBlur()
@@ -428,8 +504,10 @@ private extension AppViewController
self.navigationBarAnimator = UIViewPropertyAnimator(duration: 1.0, curve: .linear) { [weak self] in
self?.showNavigationBar()
self?.navigationController?.navigationBar.tintColor = self?.app.tintColor
self?.navigationController?.navigationBar.barTintColor = nil
// Must call layoutIfNeeded() to animate appearance change.
self?.navigationController?.navigationBar.layoutIfNeeded()
self?.contentViewController.view.layer.cornerRadius = 0
}
@@ -441,11 +519,13 @@ private extension AppViewController
func resetNavigationBarAnimation()
{
guard self.navigationBarAnimator != nil else { return }
self.navigationBarAnimator?.stopAnimation(true)
self.navigationBarAnimator = nil
self.hideNavigationBar()
self.navigationController?.navigationBar.barTintColor = .white
self.contentViewController.view.layer.cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
}
}
@@ -461,7 +541,14 @@ extension AppViewController
{
if let installedApp = self.app.installedApp
{
self.open(installedApp)
if let latestVersion = self.app.latestAvailableVersion, installedApp.hasUpdate
{
self.updateApp(installedApp, to: latestVersion)
}
else
{
self.open(installedApp)
}
}
else
{
@@ -473,36 +560,72 @@ extension AppViewController
{
guard self.app.installedApp == nil else { return }
let progress = AppManager.shared.install(self.app, presentingViewController: self) { (result) in
do
{
_ = try result.get()
}
catch OperationError.cancelled
{
// Ignore
}
catch
{
Task<Void, Never>(priority: .userInitiated) {
let group = await AppManager.shared.installAsync(self.app, presentingViewController: self) { (result) in
do
{
_ = try result.get()
}
catch OperationError.cancelled
{
// Ignore
}
catch
{
DispatchQueue.main.async {
let toastView = ToastView(error: error)
toastView.opensErrorLog = true
toastView.show(in: self)
}
}
DispatchQueue.main.async {
let toastView = ToastView(text: error.localizedDescription, detailText: nil)
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2)
self.bannerView.button.progress = nil
self.navigationBarDownloadButton.progress = nil
self.update()
}
}
DispatchQueue.main.async {
self.downloadButton.progress = nil
self.update()
if !group.progress.isCancelled
{
self.bannerView.button.progress = group.progress
self.navigationBarDownloadButton.progress = group.progress
}
}
self.downloadButton.progress = progress
}
func open(_ installedApp: InstalledApp)
{
UIApplication.shared.open(installedApp.openAppURL)
}
func updateApp(_ installedApp: InstalledApp, to version: AppVersion)
{
let previousProgress = AppManager.shared.installationProgress(for: installedApp)
guard previousProgress == nil else {
//TODO: Handle cancellation
//previousProgress?.cancel()
return
}
AppManager.shared.update(installedApp, to: version, presentingViewController: self) { (result) in
DispatchQueue.main.async {
switch result
{
case .success: print("Updated app from AppViewController:", installedApp.bundleIdentifier)
case .failure(OperationError.cancelled): break
case .failure(let error):
let toastView = ToastView(error: error)
toastView.opensErrorLog = true
toastView.show(in: self)
}
self.update()
}
}
self.update()
}
}
private extension AppViewController
@@ -522,6 +645,15 @@ private extension AppViewController
self._shouldResetLayout = true
self.view.setNeedsLayout()
}
@objc func didBecomeActive(_ notification: Notification)
{
guard let navigationController = self.navigationController, navigationController.topViewController == self else { return }
// Fixes Navigation Bar appearing after app becomes inactive -> active again.
self._shouldResetLayout = true
self.view.setNeedsLayout()
}
}
extension AppViewController: UIScrollViewDelegate

View File

@@ -1,25 +0,0 @@
//
// PermissionPopoverViewController.swift
// AltStore
//
// Created by Riley Testut on 7/23/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
class PermissionPopoverViewController: UIViewController
{
var permission: AppPermission!
@IBOutlet private var nameLabel: UILabel!
@IBOutlet private var descriptionLabel: UILabel!
override func viewDidLoad()
{
super.viewDidLoad()
self.nameLabel.text = self.permission.type.localizedName
self.descriptionLabel.text = self.permission.usageDescription
}
}

View File

@@ -0,0 +1,153 @@
//
// AppScreenshotCollectionViewCell.swift
// AltStore
//
// Created by Riley Testut on 10/11/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import UIKit
import AltStoreCore
extension AppScreenshotCollectionViewCell
{
private class ImageView: UIImageView
{
override func layoutSubviews()
{
super.layoutSubviews()
// Explicitly layout cell to ensure rounded corners are accurate.
self.superview?.superview?.setNeedsLayout()
}
}
}
class AppScreenshotCollectionViewCell: UICollectionViewCell
{
let imageView: UIImageView
var aspectRatio: CGSize = AppScreenshot.defaultAspectRatio {
didSet {
self.updateAspectRatio()
}
}
private var isRounded: Bool = false {
didSet {
self.setNeedsLayout()
self.layoutIfNeeded()
}
}
private var aspectRatioConstraint: NSLayoutConstraint?
override init(frame: CGRect)
{
self.imageView = ImageView(frame: .zero)
self.imageView.clipsToBounds = true
self.imageView.layer.cornerCurve = .continuous
self.imageView.layer.borderColor = UIColor.tertiaryLabel.cgColor
super.init(frame: frame)
self.imageView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(self.imageView)
let widthConstraint = self.imageView.widthAnchor.constraint(equalTo: self.contentView.widthAnchor)
widthConstraint.priority = .defaultHigh
let heightConstraint = self.imageView.heightAnchor.constraint(equalTo: self.contentView.heightAnchor)
heightConstraint.priority = .defaultHigh
NSLayoutConstraint.activate([
widthConstraint,
heightConstraint,
self.imageView.widthAnchor.constraint(lessThanOrEqualTo: self.contentView.widthAnchor),
self.imageView.heightAnchor.constraint(lessThanOrEqualTo: self.contentView.heightAnchor),
self.imageView.centerXAnchor.constraint(equalTo: self.contentView.centerXAnchor),
self.imageView.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor)
])
self.updateAspectRatio()
self.updateTraits()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
{
super.traitCollectionDidChange(previousTraitCollection)
self.updateTraits()
}
override func layoutSubviews()
{
super.layoutSubviews()
if self.isRounded
{
let cornerRadius = self.imageView.bounds.width / 9.0 // Based on iPhone 15
self.imageView.layer.cornerRadius = cornerRadius
}
else
{
self.imageView.layer.cornerRadius = 5
}
}
}
extension AppScreenshotCollectionViewCell
{
func setImage(_ image: UIImage?)
{
guard var image, let cgImage = image.cgImage else {
self.imageView.image = image
return
}
if image.size.width > image.size.height && self.aspectRatio.width < self.aspectRatio.height
{
// Image is landscape, but cell has portrait aspect ratio, so rotate image to match.
image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .right)
}
self.imageView.image = image
}
}
private extension AppScreenshotCollectionViewCell
{
func updateAspectRatio()
{
self.aspectRatioConstraint?.isActive = false
self.aspectRatioConstraint = self.imageView.widthAnchor.constraint(equalTo: self.imageView.heightAnchor, multiplier: self.aspectRatio.width / self.aspectRatio.height)
self.aspectRatioConstraint?.isActive = true
let aspectRatio: Double
if self.aspectRatio.width > self.aspectRatio.height
{
aspectRatio = self.aspectRatio.height / self.aspectRatio.width
}
else
{
aspectRatio = self.aspectRatio.width / self.aspectRatio.height
}
let tolerance = 0.001 as Double
let modernAspectRatio = AppScreenshot.defaultAspectRatio.width / AppScreenshot.defaultAspectRatio.height
let isRounded = (aspectRatio >= modernAspectRatio - tolerance) && (aspectRatio <= modernAspectRatio + tolerance)
self.isRounded = isRounded
}
func updateTraits()
{
let displayScale = (self.traitCollection.displayScale == 0.0) ? 1.0 : self.traitCollection.displayScale
self.imageView.layer.borderWidth = 1.0 / displayScale
}
}

View File

@@ -0,0 +1,185 @@
//
// AppScreenshotsViewController.swift
// AltStore
//
// Created by Riley Testut on 9/18/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import UIKit
import AltStoreCore
import Roxas
import Nuke
class AppScreenshotsViewController: UICollectionViewController
{
let app: StoreApp
private lazy var dataSource = self.makeDataSource()
init?(app: StoreApp, coder: NSCoder)
{
self.app = app
super.init(coder: coder)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad()
{
super.viewDidLoad()
self.collectionView.showsHorizontalScrollIndicator = false
// Allow parent background color to show through.
self.collectionView.backgroundColor = nil
// Match the parent table view margins.
self.collectionView.directionalLayoutMargins.top = 0
self.collectionView.directionalLayoutMargins.bottom = 0
self.collectionView.directionalLayoutMargins.leading = 20
self.collectionView.directionalLayoutMargins.trailing = 20
let collectionViewLayout = self.makeLayout()
self.collectionView.collectionViewLayout = collectionViewLayout
self.collectionView.register(AppScreenshotCollectionViewCell.self, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier)
self.collectionView.dataSource = self.dataSource
self.collectionView.prefetchDataSource = self.dataSource
}
}
private extension AppScreenshotsViewController
{
func makeLayout() -> UICollectionViewCompositionalLayout
{
let layoutConfig = UICollectionViewCompositionalLayoutConfiguration()
layoutConfig.contentInsetsReference = .layoutMargins
let preferredHeight = 400.0
let estimatedWidth = preferredHeight * (AppScreenshot.defaultAspectRatio.width / AppScreenshot.defaultAspectRatio.height)
let layout = UICollectionViewCompositionalLayout(sectionProvider: { [dataSource] (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
let screenshotWidths = dataSource.items.map { screenshot in
var aspectRatio = screenshot.size ?? AppScreenshot.defaultAspectRatio
if aspectRatio.width > aspectRatio.height
{
aspectRatio = CGSize(width: aspectRatio.height, height: aspectRatio.width)
}
let screenshotWidth = (preferredHeight * (aspectRatio.width / aspectRatio.height)).rounded()
return screenshotWidth
}
let smallestWidth = screenshotWidths.sorted().first
let itemWidth = smallestWidth ?? estimatedWidth // Use smallestWidth to ensure we never overshoot an item when paging.
let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(itemWidth), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .estimated(itemWidth), heightDimension: .absolute(preferredHeight))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let layoutSection = NSCollectionLayoutSection(group: group)
layoutSection.interGroupSpacing = 10
layoutSection.orthogonalScrollingBehavior = .groupPaging
return layoutSection
}, configuration: layoutConfig)
return layout
}
func makeDataSource() -> RSTArrayCollectionViewPrefetchingDataSource<AppScreenshot, UIImage>
{
let screenshots = self.app.preferredScreenshots()
let dataSource = RSTArrayCollectionViewPrefetchingDataSource<AppScreenshot, UIImage>(items: screenshots)
dataSource.cellConfigurationHandler = { [weak self] (cell, screenshot, indexPath) in
let cell = cell as! AppScreenshotCollectionViewCell
cell.imageView.isIndicatingActivity = true
cell.setImage(nil)
var aspectRatio = screenshot.size ?? AppScreenshot.defaultAspectRatio
if aspectRatio.width > aspectRatio.height
{
switch screenshot.deviceType
{
case .iphone:
// Always rotate landscape iPhone screenshots regardless of horizontal size class.
aspectRatio = CGSize(width: aspectRatio.height, height: aspectRatio.width)
case .ipad where self?.traitCollection.horizontalSizeClass == .compact:
// Only rotate landscape iPad screenshots if we're in horizontally compact environment.
aspectRatio = CGSize(width: aspectRatio.height, height: aspectRatio.width)
default: break
}
}
cell.aspectRatio = aspectRatio
}
dataSource.prefetchHandler = { (screenshot, indexPath, completionHandler) in
let imageURL = screenshot.imageURL
return RSTAsyncBlockOperation() { (operation) in
let request = ImageRequest(url: imageURL)
ImagePipeline.shared.loadImage(with: request, progress: nil) { result in
guard !operation.isCancelled else { return operation.finish() }
switch result
{
case .success(let response): completionHandler(response.image, nil)
case .failure(let error): completionHandler(nil, error)
}
}
}
}
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
let cell = cell as! AppScreenshotCollectionViewCell
cell.imageView.isIndicatingActivity = false
cell.setImage(image)
if let error = error
{
print("Error loading image:", error)
}
}
return dataSource
}
}
extension AppScreenshotsViewController
{
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
let screenshot = self.dataSource.item(at: indexPath)
let previewViewController = PreviewAppScreenshotsViewController(app: self.app)
previewViewController.currentScreenshot = screenshot
let navigationController = UINavigationController(rootViewController: previewViewController)
navigationController.modalPresentationStyle = .fullScreen
self.present(navigationController, animated: true)
}
}
@available(iOS 17, *)
#Preview(traits: .portrait) {
DatabaseManager.shared.startForPreview()
let fetchRequest = StoreApp.fetchRequest()
let storeApp = try! DatabaseManager.shared.viewContext.fetch(fetchRequest).first!
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let appViewConttroller = storyboard.instantiateViewController(withIdentifier: "appViewController") as! AppViewController
appViewConttroller.app = storeApp
let navigationController = UINavigationController(rootViewController: appViewConttroller)
return navigationController
}

View File

@@ -0,0 +1,188 @@
//
// PreviewAppScreenshotsViewController.swift
// AltStore
//
// Created by Riley Testut on 9/19/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import UIKit
import AltStoreCore
import Roxas
import Nuke
class PreviewAppScreenshotsViewController: UICollectionViewController
{
let app: StoreApp
var currentScreenshot: AppScreenshot?
private lazy var dataSource = self.makeDataSource()
init(app: StoreApp)
{
self.app = app
super.init(collectionViewLayout: UICollectionViewFlowLayout())
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad()
{
super.viewDidLoad()
let tintColor = self.app.tintColor ?? .altPrimary
self.navigationController?.view.tintColor = tintColor
self.view.backgroundColor = .systemBackground
self.collectionView.backgroundColor = nil
let collectionViewLayout = self.makeLayout()
self.collectionView.collectionViewLayout = collectionViewLayout
self.collectionView.directionalLayoutMargins.leading = 20
self.collectionView.directionalLayoutMargins.trailing = 20
self.collectionView.preservesSuperviewLayoutMargins = true
self.collectionView.insetsLayoutMarginsFromSafeArea = true
self.collectionView.alwaysBounceVertical = false
self.collectionView.register(AppScreenshotCollectionViewCell.self, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier)
self.collectionView.dataSource = self.dataSource
self.collectionView.prefetchDataSource = self.dataSource
let doneButton = UIBarButtonItem(systemItem: .done, primaryAction: UIAction { [weak self] _ in
self?.dismissPreview()
})
self.navigationItem.rightBarButtonItem = doneButton
let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(PreviewAppScreenshotsViewController.dismissPreview))
swipeGestureRecognizer.direction = .down
self.view.addGestureRecognizer(swipeGestureRecognizer)
}
override func viewIsAppearing(_ animated: Bool)
{
super.viewIsAppearing(animated)
if let screenshot = self.currentScreenshot, let index = self.dataSource.items.firstIndex(of: screenshot)
{
let indexPath = IndexPath(item: index, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
}
}
}
private extension PreviewAppScreenshotsViewController
{
func makeLayout() -> UICollectionViewCompositionalLayout
{
let layoutConfig = UICollectionViewCompositionalLayoutConfiguration()
layoutConfig.contentInsetsReference = .none
let layout = UICollectionViewCompositionalLayout(sectionProvider: { [weak self] (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
guard let self else { return nil }
let contentInsets = self.collectionView.directionalLayoutMargins
let groupWidth = layoutEnvironment.container.contentSize.width - (contentInsets.leading + contentInsets.trailing)
let groupHeight = layoutEnvironment.container.contentSize.height - (contentInsets.top + contentInsets.bottom)
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(groupWidth), heightDimension: .absolute(groupHeight))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let layoutSection = NSCollectionLayoutSection(group: group)
layoutSection.orthogonalScrollingBehavior = .groupPagingCentered
layoutSection.interGroupSpacing = 10
return layoutSection
}, configuration: layoutConfig)
return layout
}
func makeDataSource() -> RSTArrayCollectionViewPrefetchingDataSource<AppScreenshot, UIImage>
{
let screenshots = self.app.preferredScreenshots()
let dataSource = RSTArrayCollectionViewPrefetchingDataSource<AppScreenshot, UIImage>(items: screenshots)
dataSource.cellConfigurationHandler = { [weak self] (cell, screenshot, indexPath) in
let cell = cell as! AppScreenshotCollectionViewCell
cell.imageView.isIndicatingActivity = true
cell.setImage(nil)
var aspectRatio = screenshot.size ?? AppScreenshot.defaultAspectRatio
if aspectRatio.width > aspectRatio.height
{
switch screenshot.deviceType
{
case .iphone:
// Always rotate landscape iPhone screenshots regardless of horizontal size class.
aspectRatio = CGSize(width: aspectRatio.height, height: aspectRatio.width)
case .ipad where self?.traitCollection.horizontalSizeClass == .compact:
// Only rotate landscape iPad screenshots if we're in horizontally compact environment.
aspectRatio = CGSize(width: aspectRatio.height, height: aspectRatio.width)
default: break
}
}
cell.aspectRatio = aspectRatio
}
dataSource.prefetchHandler = { (screenshot, indexPath, completionHandler) in
let imageURL = screenshot.imageURL
return RSTAsyncBlockOperation() { (operation) in
let request = ImageRequest(url: imageURL)
ImagePipeline.shared.loadImage(with: request, progress: nil) { result in
guard !operation.isCancelled else { return operation.finish() }
switch result
{
case .success(let response): completionHandler(response.image, nil)
case .failure(let error): completionHandler(nil, error)
}
}
}
}
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
let cell = cell as! AppScreenshotCollectionViewCell
cell.imageView.isIndicatingActivity = false
cell.setImage(image)
if let error = error
{
print("Error loading image:", error)
}
}
return dataSource
}
}
private extension PreviewAppScreenshotsViewController
{
@objc func dismissPreview()
{
self.dismiss(animated: true)
}
}
@available(iOS 17, *)
#Preview(traits: .portrait) {
DatabaseManager.shared.startForPreview()
let fetchRequest = StoreApp.fetchRequest()
let storeApp = try! DatabaseManager.shared.viewContext.fetch(fetchRequest).first!
let previewViewController = PreviewAppScreenshotsViewController(app: storeApp)
let navigationController = UINavigationController(rootViewController: previewViewController)
return navigationController
}

View File

@@ -0,0 +1,254 @@
//
// AppIDsViewController.swift
// AltStore
//
// Created by Riley Testut on 1/27/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import UIKit
import AltStoreCore
import Roxas
final class AppIDsViewController: UICollectionViewController
{
private lazy var dataSource = self.makeDataSource()
private var didInitialFetch = false
private var isLoading = false {
didSet {
self.update()
}
}
@IBOutlet var activityIndicatorBarButtonItem: UIBarButtonItem!
override func viewDidLoad()
{
super.viewDidLoad()
self.collectionView.dataSource = self.dataSource
self.activityIndicatorBarButtonItem.isIndicatingActivity = true
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(AppIDsViewController.fetchAppIDs), for: .primaryActionTriggered)
self.collectionView.refreshControl = refreshControl
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
if !self.didInitialFetch
{
self.fetchAppIDs()
}
}
}
private extension AppIDsViewController
{
func makeDataSource() -> RSTFetchedResultsCollectionViewDataSource<AppID>
{
let fetchRequest = AppID.fetchRequest() as NSFetchRequest<AppID>
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \AppID.name, ascending: true),
NSSortDescriptor(keyPath: \AppID.bundleIdentifier, ascending: true),
NSSortDescriptor(keyPath: \AppID.expirationDate, ascending: true)]
fetchRequest.returnsObjectsAsFaults = false
if let team = DatabaseManager.shared.activeTeam()
{
fetchRequest.predicate = NSPredicate(format: "%K == %@", #keyPath(AppID.team), team)
}
else
{
fetchRequest.predicate = NSPredicate(value: false)
}
let dataSource = RSTFetchedResultsCollectionViewDataSource<AppID>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
dataSource.proxy = self
dataSource.cellConfigurationHandler = { (cell, appID, indexPath) in
let tintColor = UIColor.altPrimary
let cell = cell as! AppBannerCollectionViewCell
cell.tintColor = tintColor
cell.contentView.preservesSuperviewLayoutMargins = false
cell.contentView.layoutMargins = UIEdgeInsets(top: 0, left: self.view.layoutMargins.left, bottom: 0, right: self.view.layoutMargins.right)
cell.bannerView.iconImageView.isHidden = true
cell.bannerView.button.isIndicatingActivity = false
cell.bannerView.buttonLabel.text = NSLocalizedString("Expires in", comment: "")
let attributedAccessibilityLabel = NSMutableAttributedString(string: appID.name + ". ")
if let expirationDate = appID.expirationDate
{
cell.bannerView.button.isHidden = false
cell.bannerView.button.isUserInteractionEnabled = false
cell.bannerView.buttonLabel.isHidden = false
let currentDate = Date()
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.includesApproximationPhrase = false
formatter.includesTimeRemainingPhrase = false
formatter.allowedUnits = [.minute, .hour, .day]
formatter.maximumUnitCount = 1
let timeInterval = formatter.string(from: currentDate, to: expirationDate)
let timeIntervalText = timeInterval ?? NSLocalizedString("Unknown", comment: "")
cell.bannerView.button.setTitle(timeIntervalText.uppercased(), for: .normal)
// formatter.includesTimeRemainingPhrase = true
attributedAccessibilityLabel.mutableString.append(timeIntervalText)
}
else
{
cell.bannerView.button.isHidden = true
cell.bannerView.button.isUserInteractionEnabled = true
cell.bannerView.buttonLabel.isHidden = true
}
cell.bannerView.titleLabel.text = appID.name
cell.bannerView.subtitleLabel.text = appID.bundleIdentifier
cell.bannerView.subtitleLabel.numberOfLines = 2
cell.bannerView.subtitleLabel.minimumScaleFactor = 1.0 // Disable font shrinking
let attributedBundleIdentifier = NSMutableAttributedString(string: appID.bundleIdentifier.lowercased(), attributes: [.accessibilitySpeechPunctuation: true])
if let team = appID.team, let range = attributedBundleIdentifier.string.range(of: team.identifier.lowercased())
{
// Prefer to speak the team ID one character at a time.
let nsRange = NSRange(range, in: attributedBundleIdentifier.string)
attributedBundleIdentifier.addAttributes([.accessibilitySpeechSpellOut: true], range: nsRange)
}
attributedAccessibilityLabel.append(attributedBundleIdentifier)
cell.bannerView.accessibilityAttributedLabel = attributedAccessibilityLabel
// Make sure refresh button is correct size.
cell.layoutIfNeeded()
}
return dataSource
}
@objc func fetchAppIDs()
{
guard !self.isLoading else { return }
self.isLoading = true
AppManager.shared.fetchAppIDs { (result) in
do
{
let (_, context) = try result.get()
try context.save()
}
catch
{
DispatchQueue.main.async {
let toastView = ToastView(error: error)
toastView.show(in: self)
}
}
DispatchQueue.main.async {
self.isLoading = false
}
}
}
func update()
{
if !self.isLoading
{
self.collectionView.refreshControl?.endRefreshing()
self.activityIndicatorBarButtonItem.isIndicatingActivity = false
}
}
}
extension AppIDsViewController: UICollectionViewDelegateFlowLayout
{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
return CGSize(width: collectionView.bounds.width, height: 80)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize
{
// let indexPath = IndexPath(row: 0, section: section)
// let headerView = self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionHeader, at: indexPath)
// // Use this view to calculate the optimal size based on the collection view's width
// let size = headerView.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingCompressedSize.height),
// withHorizontalFittingPriority: .required, // Width is fixed
// verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed
// return size
// NOTE: double dequeue of cell has been discontinued
// TODO: Using harcoded value until this is fixed
return CGSize(width: collectionView.bounds.width, height: 200)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize
{
return CGSize(width: collectionView.bounds.width, height: 50)
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
{
switch kind
{
case UICollectionView.elementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "Header", for: indexPath) as! TextCollectionReusableView
headerView.layoutMargins.left = self.view.layoutMargins.left
headerView.layoutMargins.right = self.view.layoutMargins.right
if let activeTeam = DatabaseManager.shared.activeTeam(), activeTeam.type == .free
{
let text = NSLocalizedString("""
Each app and app extension installed with SideStore must register an App ID with Apple. Apple limits non-developer Apple IDs to 10 App IDs at a time.
**App IDs can't be deleted**, but they do expire after one week. SideStore will automatically renew App IDs for all active apps once they've expired.
""", comment: "")
let attributedText = NSAttributedString(markdownRepresentation: text, attributes: [.font: headerView.textLabel.font as Any])
headerView.textLabel.attributedText = attributedText
}
else
{
headerView.textLabel.text = NSLocalizedString("""
Each app and app extension installed with SideStore must register an App ID with Apple.
App IDs for paid developer accounts never expire, and there is no limit to how many you can create.
""", comment: "")
}
return headerView
case UICollectionView.elementKindSectionFooter:
let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "Footer", for: indexPath) as! TextCollectionReusableView
let count = self.dataSource.itemCount
if count == 1
{
footerView.textLabel.text = NSLocalizedString("1 App ID", comment: "")
}
else
{
footerView.textLabel.text = String(format: NSLocalizedString("%@ App IDs", comment: ""), NSNumber(value: count))
}
return footerView
default: fatalError()
}
}
}

View File

@@ -0,0 +1,17 @@
//
// AppConstants.swift
// SideStore
//
// Created by Joseph Mattiello on 11/7/22.
// Copyright © 2022 Riley Testut. All rights reserved.
//
import Foundation
public enum AppConstants {
enum Proxy {
static let address = "127.0.0.1"
static let port = "51820"
static let serverURL = "\(address):\(port)"
}
}

View File

@@ -9,66 +9,98 @@
import UIKit
import UserNotifications
import AVFoundation
import Intents
import AltStoreCore
import AltSign
import AltKit
import Roxas
private enum RefreshError: LocalizedError
{
case noInstalledApps
var errorDescription: String? {
switch self
{
case .noInstalledApps: return NSLocalizedString("No installed apps to refresh.", comment: "")
}
}
}
private extension CFNotificationName
{
static let requestAppState = CFNotificationName("com.altstore.RequestAppState" as CFString)
static let appIsRunning = CFNotificationName("com.altstore.AppState.Running" as CFString)
static func requestAppState(for appID: String) -> CFNotificationName
{
let name = String(CFNotificationName.requestAppState.rawValue) + "." + appID
return CFNotificationName(name as CFString)
}
static func appIsRunning(for appID: String) -> CFNotificationName
{
let name = String(CFNotificationName.appIsRunning.rawValue) + "." + appID
return CFNotificationName(name as CFString)
}
}
import Nuke
private let ReceivedApplicationState: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void =
{ (center, observer, name, object, userInfo) in
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let name = name else { return }
appDelegate.receivedApplicationState(notification: name)
}
extension UIApplication: LegacyBackgroundFetching {}
extension AppDelegate
{
static let openPatreonSettingsDeepLinkNotification = Notification.Name("openPatreonSettingsDeepLinkNotification")
static let openPatreonSettingsDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".OpenPatreonSettingsDeepLinkNotification")
static let importAppDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".ImportAppDeepLinkNotification")
static let addSourceDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".AddSourceDeepLinkNotification")
static let appBackupDidFinish = Notification.Name(Bundle.Info.appbundleIdentifier + ".AppBackupDidFinish")
static let exportCertificateNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".ExportCertificateNotification")
static let importAppDeepLinkURLKey = "fileURL"
static let appBackupResultKey = "result"
static let addSourceDeepLinkURLKey = "sourceURL"
static let exportCertificateCallbackTemplateKey = "callback"
}
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
private var runningApplications: Set<String>?
private let intentHandler = IntentHandler()
private let viewAppIntentHandler = ViewAppIntentHandler()
public let consoleLog = ConsoleLog()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
// navigation bar buttons spacing is too much (so hack it to use minimal spacing)
// this is swift-5 specific behavior and might change
// https://stackoverflow.com/a/64988363/11971304
//
// Warning: this affects all screens through out the app, and basically overrides storyboard
let stackViewAppearance = UIStackView.appearance(whenContainedInInstancesOf: [UINavigationBar.self])
stackViewAppearance.spacing = -8 // adjust as needed
consoleLog.startCapturing()
print("===================================================")
print("| App is Starting up |")
print("===================================================")
print("| Console Logger started capturing output streams |")
print("===================================================")
print("\n ")
// Override point for customization after application launch.
// UserDefaults.standard.setValue(true, forKey: "com.apple.CoreData.MigrationDebug")
// UserDefaults.standard.setValue(true, forKey: "com.apple.CoreData.SQLDebug")
// Register default settings before doing anything else.
UserDefaults.registerDefaults()
// Recreate Database if requested
// NOTE: Userdefaults are local to the SideStore.app sandbox and are not shared
if UserDefaults.standard.recreateDatabaseOnNextStart{
// reset the state
UserDefaults.standard.recreateDatabaseOnNextStart = false
// re-create database
DatabaseManager.recreateDatabase()
}
DatabaseManager.shared.start { (error) in
if let error = error
{
print("Failed to start DatabaseManager. Error:", error as Any)
}
else
{
print("Started DatabaseManager.")
}
}
self.setTintColor()
ServerManager.shared.startDiscovering()
UserDefaults.standard.registerDefaults()
self.prepareImageCache()
// TODO: @mahee96: find if we need to start em_proxy as in altstore?
if UserDefaults.standard.enableEMPforWireguard {
startEMProxy(bind_addr: AppConstants.Proxy.serverURL)
}
SecureValueTransformer.register()
if UserDefaults.standard.firstLaunch == nil
{
@@ -78,7 +110,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
UserDefaults.standard.preferredServerID = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.serverID) as? String
#if DEBUG || BETA
#if DEBUG && targetEnvironment(simulator)
UserDefaults.standard.isDebugModeEnabled = true
#endif
@@ -89,21 +121,74 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidEnterBackground(_ application: UIApplication)
{
ServerManager.shared.stopDiscovering()
// Make sure to update SceneDelegate.sceneDidEnterBackground() as well.
// TODO: @mahee96: find if we need to stop em_proxy as in altstore?
if UserDefaults.standard.enableEMPforWireguard {
stopEMProxy()
}
guard let oneMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date()) else { return }
let midnightOneMonthAgo = Calendar.current.startOfDay(for: oneMonthAgo)
DatabaseManager.shared.purgeLoggedErrors(before: midnightOneMonthAgo) { result in
switch result
{
case .success: break
case .failure(let error): print("[ALTLog] Failed to purge logged errors before \(midnightOneMonthAgo).", error)
}
}
}
func applicationWillEnterForeground(_ application: UIApplication)
{
AppManager.shared.update()
ServerManager.shared.startDiscovering()
PatreonAPI.shared.refreshPatreonAccount()
if UserDefaults.standard.enableEMPforWireguard {
startEMProxy(bind_addr: AppConstants.Proxy.serverURL)
}
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
{
return self.open(url)
}
func application(_ application: UIApplication, handlerFor intent: INIntent) -> Any?
{
switch intent
{
case is RefreshAllIntent: return self.intentHandler
case is ViewAppIntent: return self.viewAppIntentHandler
default: return nil
}
}
func applicationWillTerminate(_ application: UIApplication) {
// Stop console logging and clean up resources
print("\n ")
print("===================================================")
print("| Console Logger stopped capturing output streams |")
print("===================================================")
print("| App is being terminated |")
print("===================================================")
consoleLog.stopCapturing()
}
}
extension AppDelegate
{
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration
{
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>)
{
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
private extension AppDelegate
@@ -113,16 +198,119 @@ private extension AppDelegate
self.window?.tintColor = .altPrimary
}
func open(_ url: URL) -> Bool
func prepareImageCache()
{
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false }
guard let host = components.host, host.lowercased() == "patreon" else { return false }
// Avoid caching responses twice.
DataLoader.sharedUrlCache.diskCapacity = 0
DispatchQueue.main.async {
NotificationCenter.default.post(name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
let pipeline = ImagePipeline { configuration in
do
{
let dataCache = try DataCache(name: "io.sidestore.Nuke")
dataCache.sizeLimit = 512 * 1024 * 1024 // 512MB
configuration.dataCache = dataCache
}
catch
{
Logger.main.error("Failed to create image disk cache. Falling back to URL cache. \(error.localizedDescription, privacy: .public)")
}
}
return true
ImagePipeline.shared = pipeline
if let dataCache = ImagePipeline.shared.configuration.dataCache as? DataCache, #available(iOS 15, *)
{
Logger.main.info("Current image cache size: \(dataCache.totalSize.formatted(.byteCount(style: .file)), privacy: .public)")
}
}
func open(_ url: URL) -> Bool
{
if url.isFileURL
{
guard url.pathExtension.lowercased() == "ipa" else { return false }
DispatchQueue.main.async {
NotificationCenter.default.post(name: AppDelegate.importAppDeepLinkNotification, object: nil, userInfo: [AppDelegate.importAppDeepLinkURLKey: url])
}
return true
}
else
{
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false }
guard let host = components.host?.lowercased() else { return false }
switch host
{
case "appbackupresponse":
let result: Result<Void, Error>
switch url.path.lowercased()
{
case "/success": result = .success(())
case "/failure":
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name] = $1.value } ?? [:]
guard
let errorDomain = queryItems["errorDomain"],
let errorCodeString = queryItems["errorCode"], let errorCode = Int(errorCodeString),
let errorDescription = queryItems["errorDescription"]
else { return false }
let error = NSError(domain: errorDomain, code: errorCode, userInfo: [NSLocalizedDescriptionKey: errorDescription])
result = .failure(error)
default: return false
}
NotificationCenter.default.post(name: AppDelegate.appBackupDidFinish, object: nil, userInfo: [AppDelegate.appBackupResultKey: result])
return true
case "install":
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
guard let downloadURLString = queryItems["url"], let downloadURL = URL(string: downloadURLString) else { return false }
DispatchQueue.main.async {
NotificationCenter.default.post(name: AppDelegate.importAppDeepLinkNotification, object: nil, userInfo: [AppDelegate.importAppDeepLinkURLKey: downloadURL])
}
return true
case "source":
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
guard let sourceURLString = queryItems["url"], let sourceURL = URL(string: sourceURLString) else { return false }
DispatchQueue.main.async {
NotificationCenter.default.post(name: AppDelegate.addSourceDeepLinkNotification, object: nil, userInfo: [AppDelegate.addSourceDeepLinkURLKey: sourceURL])
}
return true
case "pairing":
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
guard let callbackTemplate = queryItems["urlName"]?.removingPercentEncoding else { return false }
DispatchQueue.main.async {
exportPairingFile(callbackTemplate)
}
return true
case "certificate":
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
guard let callbackTemplate = queryItems["callback_template"]?.removingPercentEncoding else { return false }
DispatchQueue.main.async {
NotificationCenter.default.post(name: AppDelegate.exportCertificateNotification, object: nil, userInfo: [AppDelegate.exportCertificateCallbackTemplateKey: callbackTemplate])
}
return true
default: return false
}
}
}
}
@@ -131,12 +319,12 @@ extension AppDelegate
private func prepareForBackgroundFetch()
{
// "Fetch" every hour, but then refresh only those that need to be refreshed (so we don't drain the battery).
UIApplication.shared.setMinimumBackgroundFetchInterval(1 * 60 * 60)
(UIApplication.shared as LegacyBackgroundFetching).setMinimumBackgroundFetchInterval(1 * 60 * 60)
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (success, error) in
}
#if DEBUG
#if DEBUG && targetEnvironment(simulator)
UIApplication.shared.registerForRemoteNotifications()
#endif
}
@@ -158,98 +346,99 @@ extension AppDelegate
func application(_ application: UIApplication, performFetchWithCompletionHandler backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void)
{
if UserDefaults.standard.isBackgroundRefreshEnabled
if UserDefaults.standard.isBackgroundRefreshEnabled && !UserDefaults.standard.presentedLaunchReminderNotification
{
ServerManager.shared.startDiscovering()
let threeHours: TimeInterval = 3 * 60 * 60
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: threeHours, repeats: false)
if !UserDefaults.standard.presentedLaunchReminderNotification
{
let threeHours: TimeInterval = 3 * 60 * 60
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: threeHours, repeats: false)
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("App Refresh Tip", comment: "")
content.body = NSLocalizedString("The more you open AltStore, the more chances it's given to refresh apps in the background.", comment: "")
let request = UNNotificationRequest(identifier: "background-refresh-reminder5", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
UserDefaults.standard.presentedLaunchReminderNotification = true
}
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("App Refresh Tip", comment: "")
content.body = NSLocalizedString("The more you open SideStore, the more chances it's given to refresh apps in the background.", comment: "")
let request = UNNotificationRequest(identifier: "background-refresh-reminder5", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
UserDefaults.standard.presentedLaunchReminderNotification = true
}
let refreshIdentifier = UUID().uuidString
BackgroundTaskManager.shared.performExtendedBackgroundTask { (taskResult, taskCompletionHandler) in
func finish(_ result: Result<[String: Result<InstalledApp, Error>], Error>)
{
// If finish is actually called, that means an error occured during installation.
if UserDefaults.standard.isBackgroundRefreshEnabled
{
ServerManager.shared.stopDiscovering()
self.scheduleFinishedRefreshingNotification(for: result, identifier: refreshIdentifier, delay: 0)
}
taskCompletionHandler()
}
if let error = taskResult.error
{
print("Error starting extended background task. Aborting.", error)
backgroundFetchCompletionHandler(.failed)
finish(.failure(error))
taskCompletionHandler()
return
}
if !DatabaseManager.shared.isStarted
{
DatabaseManager.shared.start() { (error) in
if let error = error
if error != nil
{
backgroundFetchCompletionHandler(.failed)
finish(.failure(error))
taskCompletionHandler()
}
else
{
self.refreshApps(identifier: refreshIdentifier, backgroundFetchCompletionHandler: backgroundFetchCompletionHandler, completionHandler: finish(_:))
self.performBackgroundFetch { (backgroundFetchResult) in
backgroundFetchCompletionHandler(backgroundFetchResult)
} refreshAppsCompletionHandler: { (refreshAppsResult) in
taskCompletionHandler()
}
}
}
}
else
{
self.refreshApps(identifier: refreshIdentifier, backgroundFetchCompletionHandler: backgroundFetchCompletionHandler, completionHandler: finish(_:))
self.performBackgroundFetch { (backgroundFetchResult) in
backgroundFetchCompletionHandler(backgroundFetchResult)
} refreshAppsCompletionHandler: { (refreshAppsResult) in
taskCompletionHandler()
}
}
}
}
func performBackgroundFetch(backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void,
refreshAppsCompletionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
{
self.fetchSources { (result) in
switch result
{
case .failure: backgroundFetchCompletionHandler(.failed)
case .success: backgroundFetchCompletionHandler(.newData)
}
if !UserDefaults.standard.isBackgroundRefreshEnabled
{
refreshAppsCompletionHandler(.success([:]))
}
}
guard UserDefaults.standard.isBackgroundRefreshEnabled else { return }
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
let installedApps = InstalledApp.fetchAppsForBackgroundRefresh(in: context)
AppManager.shared.backgroundRefresh(installedApps, completionHandler: refreshAppsCompletionHandler)
}
}
}
private extension AppDelegate
{
func refreshApps(identifier: String,
backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void,
completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
func fetchSources(completionHandler: @escaping (Result<Set<Source>, Error>) -> Void)
{
var fetchSourceResult: Result<Source, Error>?
var serversResult: Result<Void, Error>?
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
AppManager.shared.fetchSource() { (result) in
fetchSourceResult = result
AppManager.shared.fetchSources() { (result) in
do
{
let source = try result.get()
let (sources, context) = try result.get()
guard let context = source.managedObjectContext else { return }
let previousUpdatesFetchRequest = InstalledApp.updatesFetchRequest() as! NSFetchRequest<NSFetchRequestResult>
let previousUpdatesFetchRequest = InstalledApp.supportedUpdatesFetchRequest() as! NSFetchRequest<NSFetchRequestResult>
previousUpdatesFetchRequest.includesPendingChanges = false
previousUpdatesFetchRequest.resultType = .dictionaryResultType
previousUpdatesFetchRequest.propertiesToFetch = [#keyPath(InstalledApp.bundleIdentifier)]
previousUpdatesFetchRequest.propertiesToFetch = [#keyPath(InstalledApp.bundleIdentifier),
#keyPath(InstalledApp.storeApp.latestSupportedVersion.version),
#keyPath(InstalledApp.storeApp.latestSupportedVersion._buildVersion)]
let previousNewsItemsFetchRequest = NewsItem.fetchRequest() as NSFetchRequest<NSFetchRequestResult>
previousNewsItemsFetchRequest.includesPendingChanges = false
@@ -261,7 +450,9 @@ private extension AppDelegate
try context.save()
let updatesFetchRequest = InstalledApp.updatesFetchRequest()
let updatesFetchRequest = InstalledApp.supportedUpdatesFetchRequest()
let newsItemsFetchRequest = NewsItem.fetchRequest() as NSFetchRequest<NewsItem>
let updates = try context.fetch(updatesFetchRequest)
@@ -269,12 +460,23 @@ private extension AppDelegate
for update in updates
{
guard !previousUpdates.contains(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier }) else { continue }
guard let storeApp = update.storeApp else { continue }
guard let storeApp = update.storeApp, let latestSupportedVersion = storeApp.latestSupportedVersion, latestSupportedVersion.isSupported else { continue }
if let previousUpdate = previousUpdates.first(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier })
{
// An update for this app was already available, so check whether the version or build version is different.
guard let previousVersion = previousUpdate[#keyPath(InstalledApp.storeApp.latestSupportedVersion.version)] else { continue }
// previousUpdate might not contain buildVersion, but if it does then map empty string to nil to match AppVersion.
let previousBuildVersion = previousUpdate[#keyPath(InstalledApp.storeApp.latestSupportedVersion._buildVersion)].map { $0.isEmpty ? nil : "" }
// Only show notification if previous latestSupportedVersion does not _exactly_ match current latestSupportedVersion.
guard previousVersion != latestSupportedVersion.version || previousBuildVersion != latestSupportedVersion.buildVersion else { continue }
}
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("New Update Available", comment: "")
content.body = String(format: NSLocalizedString("%@ %@ is now available for download.", comment: ""), update.name, storeApp.version)
content.body = String(format: NSLocalizedString("%@ %@ is now available for download.", comment: ""), update.name, latestSupportedVersion.localizedVersion)
content.sound = .default
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
@@ -294,7 +496,7 @@ private extension AppDelegate
}
else
{
content.title = NSLocalizedString("AltStore News", comment: "")
content.title = NSLocalizedString("SideStore News", comment: "")
}
content.body = newsItem.title
@@ -307,223 +509,14 @@ private extension AppDelegate
DispatchQueue.main.async {
UIApplication.shared.applicationIconBadgeNumber = updates.count
}
completionHandler(.success(sources))
}
catch
{
print("Error fetching apps:", error)
fetchSourceResult = .failure(error)
}
dispatchGroup.leave()
}
if UserDefaults.standard.isBackgroundRefreshEnabled
{
dispatchGroup.enter()
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
let installedApps = InstalledApp.fetchAppsForBackgroundRefresh(in: context)
guard !installedApps.isEmpty else {
serversResult = .success(())
dispatchGroup.leave()
completionHandler(.failure(RefreshError.noInstalledApps))
return
}
self.runningApplications = []
let identifiers = installedApps.compactMap { $0.bundleIdentifier }
print("Apps to refresh:", identifiers)
DispatchQueue.global().async {
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
for identifier in identifiers
{
let appIsRunningNotification = CFNotificationName.appIsRunning(for: identifier)
CFNotificationCenterAddObserver(notificationCenter, nil, ReceivedApplicationState, appIsRunningNotification.rawValue, nil, .deliverImmediately)
let requestAppStateNotification = CFNotificationName.requestAppState(for: identifier)
CFNotificationCenterPostNotification(notificationCenter, requestAppStateNotification, nil, nil, true)
}
}
// Wait for three seconds to:
// a) give us time to discover AltServers
// b) give other processes a chance to respond to requestAppState notification
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
context.perform {
if ServerManager.shared.discoveredServers.isEmpty
{
serversResult = .failure(ConnectionError.serverNotFound)
}
else
{
serversResult = .success(())
}
dispatchGroup.leave()
let filteredApps = installedApps.filter { !(self.runningApplications?.contains($0.bundleIdentifier) ?? false) }
print("Filtered Apps to Refresh:", filteredApps.map { $0.bundleIdentifier })
let group = AppManager.shared.refresh(filteredApps, presentingViewController: nil)
group.beginInstallationHandler = { (installedApp) in
guard installedApp.bundleIdentifier == StoreApp.altstoreAppID else { return }
// We're starting to install AltStore, which means the app is about to quit.
// So, we schedule a "refresh successful" local notification to be displayed after a delay,
// but if the app is still running, we cancel the notification.
// Then, we schedule another notification and repeat the process.
// Also since AltServer has already received the app, it can finish installing even if we're no longer running in background.
if let error = group.error
{
self.scheduleFinishedRefreshingNotification(for: .failure(error), identifier: identifier)
}
else
{
var results = group.results
results[installedApp.bundleIdentifier] = .success(installedApp)
self.scheduleFinishedRefreshingNotification(for: .success(results), identifier: identifier)
}
}
group.completionHandler = { (result) in
completionHandler(result)
}
}
}
completionHandler(.failure(error))
}
}
dispatchGroup.notify(queue: .main) {
if !UserDefaults.standard.isBackgroundRefreshEnabled
{
guard let fetchSourceResult = fetchSourceResult else {
backgroundFetchCompletionHandler(.failed)
return
}
switch fetchSourceResult
{
case .failure: backgroundFetchCompletionHandler(.failed)
case .success: backgroundFetchCompletionHandler(.newData)
}
completionHandler(.success([:]))
}
else
{
guard let fetchSourceResult = fetchSourceResult, let serversResult = serversResult else {
backgroundFetchCompletionHandler(.failed)
return
}
// Call completionHandler early to improve chances of refreshing in the background again.
switch (fetchSourceResult, serversResult)
{
case (.success, .success): backgroundFetchCompletionHandler(.newData)
case (.success, .failure(ConnectionError.serverNotFound)): backgroundFetchCompletionHandler(.newData)
case (.failure, _), (_, .failure): backgroundFetchCompletionHandler(.failed)
}
}
}
}
func receivedApplicationState(notification: CFNotificationName)
{
let baseName = String(CFNotificationName.appIsRunning.rawValue)
let appID = String(notification.rawValue).replacingOccurrences(of: baseName + ".", with: "")
self.runningApplications?.insert(appID)
}
func scheduleFinishedRefreshingNotification(for result: Result<[String: Result<InstalledApp, Error>], Error>, identifier: String, delay: TimeInterval = 5)
{
func scheduleFinishedRefreshingNotification()
{
self.cancelFinishedRefreshingNotification(identifier: identifier)
let content = UNMutableNotificationContent()
var shouldPresentAlert = true
do
{
let results = try result.get()
shouldPresentAlert = !results.isEmpty
for (_, result) in results
{
guard case let .failure(error) = result else { continue }
throw error
}
content.title = NSLocalizedString("Refreshed Apps", comment: "")
content.body = NSLocalizedString("All apps have been refreshed.", comment: "")
}
catch ConnectionError.serverNotFound
{
shouldPresentAlert = false
}
catch RefreshError.noInstalledApps
{
shouldPresentAlert = false
}
catch
{
print("Failed to refresh apps in background.", error)
content.title = NSLocalizedString("Failed to Refresh Apps", comment: "")
content.body = error.localizedDescription
shouldPresentAlert = true
}
if shouldPresentAlert
{
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: delay + 1, repeats: false)
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
if delay > 0
{
DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
UNUserNotificationCenter.current().getPendingNotificationRequests() { (requests) in
// If app is still running at this point, we schedule another notification with same identifier.
// This prevents the currently scheduled notification from displaying, and starts another countdown timer.
// First though, make sure there _is_ still a pending request, otherwise it's been cancelled
// and we should stop polling.
guard requests.contains(where: { $0.identifier == identifier }) else { return }
scheduleFinishedRefreshingNotification()
}
}
}
}
}
scheduleFinishedRefreshingNotification()
// Perform synchronously to ensure app doesn't quit before we've finishing saving to disk.
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
context.performAndWait {
_ = RefreshAttempt(identifier: identifier, result: result, context: context)
do { try context.save() }
catch { print("Failed to save refresh attempt.", error) }
}
}
func cancelFinishedRefreshingNotification(identifier: String)
{
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [identifier])
}
}

View File

@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -15,11 +13,11 @@
<scene sceneID="lNR-II-WoW">
<objects>
<navigationController storyboardIdentifier="navigationController" id="ZTo-53-dSL" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Aej-RF-PfV" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Aej-RF-PfV" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
<autoresizingMask key="autoresizingMask"/>
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="barTintColor" name="Primary"/>
<color key="barTintColor" name="SettingsBackground"/>
<textAttributes key="titleTextAttributes">
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</textAttributes>
@@ -38,7 +36,7 @@
<!--Authentication View Controller-->
<scene sceneID="OCd-xc-Ms7">
<objects>
<viewController storyboardIdentifier="authenticationViewController" id="yO1-iT-7NP" customClass="AuthenticationViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<viewController storyboardIdentifier="authenticationViewController" id="yO1-iT-7NP" customClass="AuthenticationViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="mjy-4S-hyH">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -53,30 +51,30 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="603"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" spacing="50" translatesAutoresizingMaskIntoConstraints="NO" id="YmX-7v-pxh">
<rect key="frame" x="16" y="6" width="343" height="397"/>
<rect key="frame" x="16" y="6" width="343" height="359.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="Yfu-hI-0B7" userLabel="Welcome">
<rect key="frame" x="0.0" y="0.0" width="343" height="67.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Welcome to AltStore." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="EI2-V3-zQZ">
<rect key="frame" x="0.0" y="0.0" width="333.5" height="41"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Welcome to SideStore." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="EI2-V3-zQZ">
<rect key="frame" x="0.0" y="0.0" width="343" height="41"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sign in with your Apple ID to get started." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="SNU-tv-8Au">
<rect key="frame" x="0.0" y="47" width="308.5" height="20.5"/>
<rect key="frame" x="0.0" y="47" width="306.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="32" translatesAutoresizingMaskIntoConstraints="NO" id="Aqh-MD-HFf">
<rect key="frame" x="0.0" y="117.5" width="343" height="279.5"/>
<rect key="frame" x="0.0" y="117.5" width="343" height="242"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="Oy6-xr-cZ7">
<rect key="frame" x="0.0" y="0.0" width="343" height="196.5"/>
<rect key="frame" x="0.0" y="0.0" width="343" height="159"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="H95-7V-Kk8" userLabel="Apple ID">
<rect key="frame" x="0.0" y="0.0" width="343" height="72"/>
@@ -120,7 +118,7 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="hd5-yc-rcq" userLabel="Password">
<rect key="frame" x="0.0" y="87" width="343" height="109.5"/>
<rect key="frame" x="0.0" y="87" width="343" height="72"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="lvX-im-C95">
<rect key="frame" x="0.0" y="0.0" width="343" height="17"/>
@@ -158,31 +156,19 @@
</constraints>
<edgeInsets key="layoutMargins" top="0.0" left="14" bottom="0.0" right="14"/>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="Glz-dw-2Eg">
<rect key="frame" x="0.0" y="76" width="343" height="33.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="If you used an app-specific password to install AltStore, please use that same password again." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="a51-OQ-f3j">
<rect key="frame" x="14" y="0.0" width="315" height="33.5"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" white="1" alpha="0.75" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<edgeInsets key="layoutMargins" top="0.0" left="14" bottom="0.0" right="14"/>
</stackView>
</subviews>
</stackView>
</subviews>
</stackView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2N5-zd-fUj">
<rect key="frame" x="0.0" y="228.5" width="343" height="51"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2N5-zd-fUj">
<rect key="frame" x="0.0" y="191" width="343" height="51"/>
<color key="backgroundColor" name="SettingsHighlighted"/>
<constraints>
<constraint firstAttribute="height" constant="51" id="4BK-Un-5pl"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="19"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
<state key="normal" title="Sign in">
<color key="titleColor" name="Pink"/>
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="authenticate" destination="yO1-iT-7NP" eventType="primaryActionTriggered" id="LER-a2-CbC"/>
@@ -192,7 +178,7 @@
</stackView>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="DBk-rT-ZE8">
<stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="250" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="DBk-rT-ZE8">
<rect key="frame" x="16" y="498.5" width="343" height="96.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Why do we need this?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p9U-0q-Kn8">
@@ -201,7 +187,7 @@
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="on2-62-waY">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="249" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumScaleFactor="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="on2-62-waY">
<rect key="frame" x="0.0" y="24.5" width="343" height="72"/>
<string key="text">Your Apple ID is used to configure apps so they can be installed on this device. Your credentials will be stored securely in this device's Keychain and sent only to Apple for authentication.</string>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
@@ -211,6 +197,13 @@
</subviews>
</stackView>
</subviews>
<constraints>
<constraint firstItem="DBk-rT-ZE8" firstAttribute="leading" secondItem="2wp-qG-f0Z" secondAttribute="leadingMargin" id="5AT-nV-ZP9"/>
<constraint firstAttribute="bottomMargin" secondItem="DBk-rT-ZE8" secondAttribute="bottom" id="HgY-oY-8KM"/>
<constraint firstAttribute="trailingMargin" secondItem="DBk-rT-ZE8" secondAttribute="trailing" id="VCf-bW-2K4"/>
<constraint firstItem="YmX-7v-pxh" firstAttribute="top" secondItem="2wp-qG-f0Z" secondAttribute="top" constant="6" id="iUr-Nd-tkt"/>
<constraint firstItem="DBk-rT-ZE8" firstAttribute="top" relation="greaterThanOrEqual" secondItem="YmX-7v-pxh" secondAttribute="bottom" constant="8" symbolic="YES" id="zTU-eY-DWd"/>
</constraints>
</view>
</subviews>
<constraints>
@@ -221,26 +214,22 @@
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" name="Primary"/>
<viewLayoutGuide key="safeArea" id="zMn-DV-fpy"/>
<color key="backgroundColor" name="SettingsBackground"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="WXx-hX-AXv" secondAttribute="bottom" id="0jL-Ky-ju6"/>
<constraint firstAttribute="leadingMargin" secondItem="YmX-7v-pxh" secondAttribute="leading" id="2PO-lG-dmB"/>
<constraint firstItem="DBk-rT-ZE8" firstAttribute="leading" secondItem="2wp-qG-f0Z" secondAttribute="leadingMargin" id="5AT-nV-ZP9"/>
<constraint firstItem="oyW-Fd-ojD" firstAttribute="top" secondItem="zMn-DV-fpy" secondAttribute="top" id="730-db-ukB"/>
<constraint firstItem="2wp-qG-f0Z" firstAttribute="bottomMargin" secondItem="DBk-rT-ZE8" secondAttribute="bottom" id="HgY-oY-8KM"/>
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="oyW-Fd-ojD" secondAttribute="trailing" id="KGE-CN-SWf"/>
<constraint firstItem="WXx-hX-AXv" firstAttribute="top" secondItem="mjy-4S-hyH" secondAttribute="top" id="LPQ-bF-ic0"/>
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="WXx-hX-AXv" secondAttribute="trailing" id="MG7-A6-pKp"/>
<constraint firstAttribute="trailingMargin" secondItem="YmX-7v-pxh" secondAttribute="trailing" id="O4T-nu-o3e"/>
<constraint firstItem="zMn-DV-fpy" firstAttribute="bottom" secondItem="oyW-Fd-ojD" secondAttribute="bottom" id="PuX-ab-cEq"/>
<constraint firstItem="oyW-Fd-ojD" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="SzC-gC-Nvi"/>
<constraint firstItem="2wp-qG-f0Z" firstAttribute="trailingMargin" secondItem="DBk-rT-ZE8" secondAttribute="trailing" id="VCf-bW-2K4"/>
<constraint firstItem="WXx-hX-AXv" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="d08-zF-5X6"/>
<constraint firstItem="2wp-qG-f0Z" firstAttribute="height" secondItem="oyW-Fd-ojD" secondAttribute="height" id="dFN-pw-TWt"/>
<constraint firstItem="YmX-7v-pxh" firstAttribute="top" secondItem="2wp-qG-f0Z" secondAttribute="top" constant="6" id="iUr-Nd-tkt"/>
<constraint firstItem="2wp-qG-f0Z" firstAttribute="width" secondItem="oyW-Fd-ojD" secondAttribute="width" id="rYO-GN-0Lk"/>
</constraints>
<viewLayoutGuide key="safeArea" id="zMn-DV-fpy"/>
</view>
<toolbarItems/>
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="jCf-N4-xVD">
@@ -269,7 +258,7 @@
<!--How it works-->
<scene sceneID="dMt-EA-SGy">
<objects>
<viewController storyboardIdentifier="instructionsViewController" hidesBottomBarWhenPushed="YES" id="aFi-fb-W0B" customClass="InstructionsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<viewController storyboardIdentifier="instructionsViewController" hidesBottomBarWhenPushed="YES" id="aFi-fb-W0B" customClass="InstructionsViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="Otz-hn-WGS">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -292,13 +281,13 @@
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Q20-ml-9D0">
<rect key="frame" x="79" y="16" width="264" height="64"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Launch AltServer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="XKD-XH-eB0">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Launch SideStore" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="XKD-XH-eB0">
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Leave AltServer running in the background on your computer." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="6HP-Xh-sAH">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Leave SideStore running in the background on your idevice." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="6HP-Xh-sAH">
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -323,13 +312,13 @@
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="dMu-eg-gIO">
<rect key="frame" x="79" y="16" width="264" height="64"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Connect to WiFi" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="esj-pD-D4A">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Connect to Wi-Fi and VPN" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="esj-pD-D4A">
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable iTunes WiFi Sync and connect to the same WiFi as AltServer." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="4rk-ge-FSj">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable LocalDevVPN and use Sidestore on the go." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="4rk-ge-FSj">
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -360,7 +349,7 @@
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Browse and download apps directly from AltStore." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="M7T-9j-uyt">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Browse and download apps directly from SideStore." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="M7T-9j-uyt">
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -383,7 +372,7 @@
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Xs6-pJ-PUz">
<rect key="frame" x="79" y="16" width="264" height="64"/>
<rect key="frame" x="79" y="17" width="264" height="62"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps Refresh Automatically" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="nvb-Aq-sYa">
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
@@ -391,8 +380,8 @@
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps are refreshed in the background when on same WiFi as AltServer." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="HU5-Hv-E3d">
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps are refreshed in the background while you are on SideStore VPN!" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="HU5-Hv-E3d">
<rect key="frame" x="0.0" y="25.5" width="264" height="36.5"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
@@ -406,20 +395,21 @@
</stackView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qZ9-AR-2zK">
<rect key="frame" x="16" y="608" width="343" height="51"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="SettingsHighlighted"/>
<constraints>
<constraint firstAttribute="height" constant="51" id="LQz-qG-ZJK"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="19"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
<state key="normal" title="Got it">
<color key="titleColor" name="Pink"/>
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="dismiss" destination="aFi-fb-W0B" eventType="primaryActionTriggered" id="sBq-zj-Mln"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" name="Primary"/>
<viewLayoutGuide key="safeArea" id="Zek-aC-HOO"/>
<color key="backgroundColor" name="SettingsBackground"/>
<constraints>
<constraint firstItem="qZ9-AR-2zK" firstAttribute="top" secondItem="bp6-55-IG2" secondAttribute="bottom" id="3yt-cr-swd"/>
<constraint firstItem="bp6-55-IG2" firstAttribute="top" secondItem="Zek-aC-HOO" secondAttribute="top" id="42S-q2-YZn"/>
@@ -429,7 +419,6 @@
<constraint firstAttribute="bottomMargin" secondItem="qZ9-AR-2zK" secondAttribute="bottom" id="e8e-9l-Mkt"/>
<constraint firstItem="qZ9-AR-2zK" firstAttribute="leading" secondItem="Otz-hn-WGS" secondAttribute="leadingMargin" id="t2b-3e-6ld"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Zek-aC-HOO"/>
</view>
<navigationItem key="navigationItem" title="How it works" largeTitleDisplayMode="always" id="bCq-Jq-gf1"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
@@ -442,14 +431,138 @@
</objects>
<point key="canvasLocation" x="1353" y="736"/>
</scene>
<!--Refresh SideStore-->
<scene sceneID="9Vh-dM-OqX">
<objects>
<viewController storyboardIdentifier="refreshAltStoreViewController" id="aoK-yE-UVT" customClass="RefreshAltStoreViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="R83-kV-365">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fpO-Bf-gFY" customClass="RSTPlaceholderView">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="tDQ-ao-1Jg">
<rect key="frame" x="16" y="570" width="343" height="89"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xcg-hT-tDe" customClass="PillButton" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="343" height="51"/>
<color key="backgroundColor" name="SettingsHighlighted"/>
<constraints>
<constraint firstAttribute="height" constant="51" id="SJA-N9-Z6u"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
<color key="tintColor" name="SettingsHighlighted"/>
<state key="normal" title="Refresh Now">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="refreshAltStore:" destination="aoK-yE-UVT" eventType="primaryActionTriggered" id="WQu-9b-Zgg"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qua-VA-asJ">
<rect key="frame" x="0.0" y="59" width="343" height="30"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" title="Refresh Later">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="cancel:" destination="aoK-yE-UVT" eventType="primaryActionTriggered" id="ffO-0a-LdE"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="iwE-xE-ziz"/>
<color key="backgroundColor" name="SettingsBackground"/>
<constraints>
<constraint firstItem="fpO-Bf-gFY" firstAttribute="leading" secondItem="iwE-xE-ziz" secondAttribute="leading" id="A77-nX-Wg2"/>
<constraint firstAttribute="trailingMargin" secondItem="tDQ-ao-1Jg" secondAttribute="trailing" id="KPg-sO-Rnc"/>
<constraint firstItem="fpO-Bf-gFY" firstAttribute="trailing" secondItem="iwE-xE-ziz" secondAttribute="trailing" id="SGI-1D-Eaw"/>
<constraint firstItem="fpO-Bf-gFY" firstAttribute="bottom" secondItem="R83-kV-365" secondAttribute="bottom" id="cHl-7X-dW1"/>
<constraint firstAttribute="bottomMargin" secondItem="tDQ-ao-1Jg" secondAttribute="bottom" id="kLN-e7-BJE"/>
<constraint firstItem="fpO-Bf-gFY" firstAttribute="top" secondItem="R83-kV-365" secondAttribute="top" id="oKo-10-7kD"/>
<constraint firstItem="tDQ-ao-1Jg" firstAttribute="leading" secondItem="R83-kV-365" secondAttribute="leadingMargin" id="zEt-Xr-kJx"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="Refresh SideStore" largeTitleDisplayMode="always" id="5nk-NR-jtV"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
<connections>
<outlet property="placeholderView" destination="fpO-Bf-gFY" id="q7d-au-d94"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Chr-7g-qEw" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="3025" y="734"/>
</scene>
<!--Select a Team-->
<scene sceneID="ioQ-WB-CLJ">
<objects>
<viewController storyboardIdentifier="selectTeamViewController" hidesBottomBarWhenPushed="YES" id="kOD-4P-a6L" customClass="SelectTeamViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" indicatorStyle="white" dataMode="prototypes" style="grouped" separatorStyle="none" rowHeight="60" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="fWW-kX-ifH">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" name="SettingsBackground"/>
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="TeamCell" textLabel="6ip-34-gmM" detailTextLabel="knk-Wf-PKf" style="IBUITableViewCellStyleSubtitle" id="qeQ-eb-2SC" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="55.5" width="375" height="60"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qeQ-eb-2SC" id="bT4-Fc-u6I">
<rect key="frame" x="0.0" y="0.0" width="334.5" height="60"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Team 1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="6ip-34-gmM">
<rect key="frame" x="30" y="10" width="56.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Description" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="knk-Wf-PKf">
<rect key="frame" x="30" y="33.5" width="70" height="14.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="12"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="style">
<integer key="value" value="0"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
</userDefinedRuntimeAttributes>
</tableViewCell>
</prototypes>
<sections/>
<connections>
<outlet property="dataSource" destination="kOD-4P-a6L" id="OLE-fk-1MD"/>
<outlet property="delegate" destination="kOD-4P-a6L" id="t9T-jO-TrR"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="Select a Team" largeTitleDisplayMode="always" id="qxJ-Go-OPq"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="yH5-jU-aez" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2114" y="734"/>
</scene>
</scenes>
<color key="tintColor" name="Primary"/>
<resources>
<namedColor name="Pink">
<color red="0.92549019607843142" green="0.25490196078431371" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="Primary">
<color red="0.0039215686274509803" green="0.50196078431372548" blue="0.51764705882352946" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.64313725490196083" green="0.019607843137254902" blue="0.98039215686274506" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="SettingsBackground">
<color red="0.45098039215686275" green="0.015686274509803921" blue="0.68627450980392157" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="SettingsHighlighted">
<color red="0.38823529411764707" green="0.011764705882352941" blue="0.58823529411764708" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
<color key="tintColor" name="Primary"/>
</document>

View File

@@ -10,9 +10,10 @@ import UIKit
import AltSign
class AuthenticationViewController: UIViewController
final class AuthenticationViewController: UIViewController
{
var authenticationHandler: (((ALTAccount, String)?) -> Void)?
var authenticationHandler: ((String, String, @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void) -> Void)?
var completionHandler: (((ALTAccount, ALTAppleAPISession, String)?) -> Void)?
private weak var toastView: ToastView?
@@ -30,6 +31,22 @@ class AuthenticationViewController: UIViewController
{
super.viewDidLoad()
// fetch anisette servers asap when loading Auth Screen (if list is empty
if(UserDefaults.standard.menuAnisetteServersList.isEmpty){
Task{
let sourceURL = UserDefaults.standard.menuAnisetteList
do{
_ = try await AnisetteViewModel.getListOfServers(serverSource: sourceURL)
print("AuthenticationViewController: Server list refresh request completed for sourceURL: \(sourceURL)")
}catch{
print("AuthenticationViewController: Server list refresh request Failed for sourceURL: \(sourceURL) Error: \(error)")
}
}
}
self.signInButton.activityIndicatorView.style = .medium
self.signInButton.activityIndicatorView.color = .white
for view in [self.appleIDBackgroundView!, self.passwordBackgroundView!, self.signInButton!]
{
view.clipsToBounds = true
@@ -94,23 +111,30 @@ private extension AuthenticationViewController
self.signInButton.isIndicatingActivity = true
ALTAppleAPI.shared.authenticate(appleID: emailAddress, password: password) { (account, error) in
do
{
let account = try Result(account, error).get()
self.authenticationHandler?((account, password))
}
catch
self.authenticationHandler?(emailAddress, password) { (result) in
switch result
{
case .failure(ALTAppleAPIError.requiresTwoFactorAuthentication):
// Ignore
DispatchQueue.main.async {
let toastView = ToastView(text: NSLocalizedString("Failed to Log In", comment: ""), detailText: error.localizedDescription)
toastView.textLabel.textColor = .altPink
toastView.detailTextLabel.textColor = .altPink
toastView.show(in: self.navigationController?.view ?? self.view)
self.signInButton.isIndicatingActivity = false
}
case .failure(let error as NSError):
DispatchQueue.main.async {
let error = error.withLocalizedTitle(NSLocalizedString("Failed to Log In", comment: ""))
let toastView = ToastView(error: error)
toastView.show(in: self)
toastView.backgroundColor = .white
toastView.textLabel.textColor = .altPrimary
toastView.detailTextLabel.textColor = .altPrimary
self.toastView = toastView
self.signInButton.isIndicatingActivity = false
}
case .success((let account, let session)):
self.completionHandler?((account, session, password))
}
DispatchQueue.main.async {
@@ -121,7 +145,7 @@ private extension AuthenticationViewController
@IBAction func cancel(_ sender: UIBarButtonItem)
{
self.authenticationHandler?(nil)
self.completionHandler?(nil)
}
}

View File

@@ -8,7 +8,7 @@
import UIKit
class InstructionsViewController: UIViewController
final class InstructionsViewController: UIViewController
{
var completionHandler: (() -> Void)?
@@ -17,6 +17,10 @@ class InstructionsViewController: UIViewController
@IBOutlet private var contentStackView: UIStackView!
@IBOutlet private var dismissButton: UIButton!
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewDidLoad()
{
super.viewDidLoad()

View File

@@ -0,0 +1,83 @@
//
// RefreshAltStoreViewController.swift
// AltStore
//
// Created by Riley Testut on 10/26/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
import AltStoreCore
import AltSign
import Roxas
final class RefreshAltStoreViewController: UIViewController
{
var context: AuthenticatedOperationContext!
var completionHandler: ((Result<Void, Error>) -> Void)?
@IBOutlet private var placeholderView: RSTPlaceholderView!
override func viewDidLoad()
{
super.viewDidLoad()
self.placeholderView.textLabel.isHidden = true
self.placeholderView.detailTextLabel.textAlignment = .left
self.placeholderView.detailTextLabel.textColor = UIColor.white.withAlphaComponent(0.6)
self.placeholderView.detailTextLabel.text = NSLocalizedString("SideStore was unable to use an existing signing certificate, so it had to create a new one. This will cause any apps installed with an existing certificate to expire — including SideStore.\n\nTo prevent SideStore from expiring early, please refresh the app now. SideStore will quit once refreshing is complete.", comment: "")
}
}
private extension RefreshAltStoreViewController
{
@IBAction func refreshAltStore(_ sender: PillButton)
{
guard let altStore = InstalledApp.fetchAltStore(in: DatabaseManager.shared.viewContext) else { return }
func refresh()
{
sender.isIndicatingActivity = true
if let progress = AppManager.shared.installationProgress(for: altStore)
{
// Cancel pending AltStore installation so we can start a new one.
progress.cancel()
}
// Install, _not_ refresh, to ensure we are installing with a non-revoked certificate.
let group = AppManager.shared.install(altStore, presentingViewController: self, context: self.context) { (result) in
switch result
{
case .success: self.completionHandler?(.success(()))
case .failure(let error as NSError):
DispatchQueue.main.async {
sender.progress = nil
sender.isIndicatingActivity = false
let alertController = UIAlertController(title: NSLocalizedString("Failed to Refresh SideStore", comment: ""), message: error.localizedFailureReason ?? error.localizedDescription, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Try Again", comment: ""), style: .default, handler: { (action) in
refresh()
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Refresh Later", comment: ""), style: .cancel, handler: { (action) in
self.completionHandler?(.failure(error))
}))
self.present(alertController, animated: true, completion: nil)
}
}
}
sender.progress = group.progress
}
refresh()
}
@IBAction func cancel(_ sender: UIButton)
{
self.completionHandler?(.failure(OperationError.cancelled))
}
}

View File

@@ -0,0 +1,61 @@
//
// SelectTeamViewController.swift
// AltStore
//
// Created by Megarushing on 4/26/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
import UIKit
import SafariServices
import MessageUI
import Intents
import IntentsUI
import AltSign
final class SelectTeamViewController: UITableViewController
{
public var teams: [ALTTeam]?
public var completionHandler: ((Result<ALTTeam, Swift.Error>) -> Void)?
private var prototypeHeaderFooterView: SettingsHeaderFooterView!
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return teams?.count ?? 0
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
return self.completionHandler!(.success((self.teams?[indexPath.row])!))
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TeamCell", for: indexPath) as! InsetGroupTableViewCell
cell.textLabel?.text = self.teams?[indexPath.row].name
cell.detailTextLabel?.text = self.teams?[indexPath.row].type.localizedDescription
if indexPath.row == 0
{
cell.style = InsetGroupTableViewCell.Style.top
} else if indexPath.row == self.tableView(self.tableView, numberOfRowsInSection: indexPath.section) - 1 {
cell.style = InsetGroupTableViewCell.Style.bottom
} else {
cell.style = InsetGroupTableViewCell.Style.middle
}
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
"Teams"
}
}

View File

@@ -1,7 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="6NO-wl-tj1">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@@ -11,15 +14,38 @@
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" name="Background"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
<tabBarItem key="tabBarItem" title="" id="RiK-sx-Kgv"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
<point key="canvasLocation" x="962.31884057971024" y="375"/>
</scene>
<!--Tab Bar Controller-->
<scene sceneID="9Yy-ze-Trt">
<objects>
<tabBarController automaticallyAdjustsScrollViewInsets="NO" id="6NO-wl-tj1" sceneMemberID="viewController">
<toolbarItems/>
<tabBar key="tabBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="4lc-l2-vDf">
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tabBar>
<connections>
<segue destination="01J-lp-oVM" kind="relationship" relationship="viewControllers" id="2qH-aa-n0z"/>
</connections>
</tabBarController>
<placeholder placeholderIdentifier="IBFirstResponder" id="pxX-hL-ovw" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="52.173913043478265" y="375"/>
</scene>
</scenes>
<resources>
<namedColor name="Background">
<color red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>

File diff suppressed because it is too large Load Diff

View File

@@ -1,120 +0,0 @@
//
// BrowseCollectionViewCell.swift
// AltStore
//
// Created by Riley Testut on 7/15/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
import Roxas
import Nuke
@objc class BrowseCollectionViewCell: UICollectionViewCell
{
var imageURLs: [URL] = [] {
didSet {
self.dataSource.items = self.imageURLs as [NSURL]
}
}
private lazy var dataSource = self.makeDataSource()
@IBOutlet var nameLabel: UILabel!
@IBOutlet var developerLabel: UILabel!
@IBOutlet var appIconImageView: UIImageView!
@IBOutlet var actionButton: PillButton!
@IBOutlet var subtitleLabel: UILabel!
@IBOutlet var screenshotsCollectionView: UICollectionView!
@IBOutlet var betaBadgeView: UIImageView!
@IBOutlet private var screenshotsContentView: UIView!
override func awakeFromNib()
{
super.awakeFromNib()
// Must be registered programmatically, not in BrowseCollectionViewCell.xib, or else it'll throw an exception 🤷.
self.screenshotsCollectionView.register(ScreenshotCollectionViewCell.self, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier)
self.screenshotsCollectionView.delegate = self
self.screenshotsCollectionView.dataSource = self.dataSource
self.screenshotsCollectionView.prefetchDataSource = self.dataSource
self.screenshotsContentView.layer.cornerRadius = 20
self.screenshotsContentView.layer.masksToBounds = true
self.update()
}
override func tintColorDidChange()
{
super.tintColorDidChange()
self.update()
}
}
private extension BrowseCollectionViewCell
{
func makeDataSource() -> RSTArrayCollectionViewPrefetchingDataSource<NSURL, UIImage>
{
let dataSource = RSTArrayCollectionViewPrefetchingDataSource<NSURL, UIImage>(items: [])
dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in
let cell = cell as! ScreenshotCollectionViewCell
cell.imageView.image = nil
cell.imageView.isIndicatingActivity = true
}
dataSource.prefetchHandler = { (imageURL, indexPath, completionHandler) in
return RSTAsyncBlockOperation() { (operation) in
ImagePipeline.shared.loadImage(with: imageURL as URL, progress: nil, completion: { (response, error) in
guard !operation.isCancelled else { return operation.finish() }
if let image = response?.image
{
completionHandler(image, nil)
}
else
{
completionHandler(nil, error)
}
})
}
}
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
let cell = cell as! ScreenshotCollectionViewCell
cell.imageView.isIndicatingActivity = false
cell.imageView.image = image
if let error = error
{
print("Error loading image:", error)
}
}
return dataSource
}
private func update()
{
self.subtitleLabel.textColor = self.tintColor
self.screenshotsContentView.backgroundColor = self.tintColor.withAlphaComponent(0.1)
}
}
extension BrowseCollectionViewCell: UICollectionViewDelegateFlowLayout
{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
// Assuming 9.0 / 16.0 ratio for now.
let aspectRatio: CGFloat = 9.0 / 16.0
let itemHeight = collectionView.bounds.height
let itemWidth = itemHeight * aspectRatio
let size = CGSize(width: itemWidth.rounded(.down), height: itemHeight.rounded(.down))
return size
}
}

View File

@@ -1,131 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="ln4-pC-7KY" customClass="BrowseCollectionViewCell" customModule="AltStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="400"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
<rect key="frame" x="0.0" y="0.0" width="375" height="400"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="11" translatesAutoresizingMaskIntoConstraints="NO" id="Y3g-Md-6xH" userLabel="App Info">
<rect key="frame" x="20" y="20" width="335" height="79"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="F2j-pX-09A" customClass="AppIconImageView" customModule="AltStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="7" width="65" height="65"/>
<constraints>
<constraint firstAttribute="width" secondItem="F2j-pX-09A" secondAttribute="height" multiplier="1:1" id="c2j-8O-Diw"/>
<constraint firstAttribute="height" constant="65" id="ufl-3d-nkT"/>
</constraints>
</imageView>
<stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="100" axis="vertical" alignment="top" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="zkp-KH-OyV">
<rect key="frame" x="76" y="21" width="176" height="37"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="Ykl-yo-ncv">
<rect key="frame" x="0.0" y="0.0" width="127.5" height="20.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" horizontalCompressionResistancePriority="500" text="App Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="xni-8I-ewW">
<rect key="frame" x="0.0" y="0.0" width="80.5" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" image="BetaBadge" translatesAutoresizingMaskIntoConstraints="NO" id="5gN-I2-QOB">
<rect key="frame" x="86.5" y="0.0" width="41" height="20.5"/>
</imageView>
</subviews>
</stackView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Developer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="B5S-HI-tWJ">
<rect key="frame" x="0.0" y="22.5" width="57.5" height="14.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DeC-Y2-fvR" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
<rect key="frame" x="263" y="24" width="72" height="31"/>
<color key="backgroundColor" red="0.22352941179999999" green="0.4941176471" blue="0.39607843139999999" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="72" id="X7D-DN-WnD"/>
<constraint firstAttribute="height" constant="31" id="svo-Sc-wpR"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
<state key="normal" title="OPEN"/>
</button>
</subviews>
</stackView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="w1r-LJ-TDs" userLabel="Screenshots">
<rect key="frame" x="15" y="114" width="345" height="266"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="hRR-84-Owd">
<rect key="frame" x="0.0" y="0.0" width="345" height="266"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Classic Nintendo games in your pocket." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="Imx-Le-bcy">
<rect key="frame" x="20" y="15" width="305" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="1" green="0.14901960780000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" contentInsetAdjustmentBehavior="never" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="RFs-qp-Ca4">
<rect key="frame" x="20" y="47" width="305" height="185"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="0.0" minimumInteritemSpacing="10" id="jH9-Jo-IHA">
<size key="itemSize" width="120" height="213"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells/>
</collectionView>
</subviews>
<edgeInsets key="layoutMargins" top="15" left="20" bottom="20" right="20"/>
</stackView>
</subviews>
<color key="backgroundColor" red="1" green="0.14901960780000001" blue="0.0" alpha="0.050000000000000003" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="hRR-84-Owd" firstAttribute="leading" secondItem="w1r-LJ-TDs" secondAttribute="leading" id="3us-zR-peW"/>
<constraint firstItem="hRR-84-Owd" firstAttribute="top" secondItem="w1r-LJ-TDs" secondAttribute="top" id="HWW-aS-Scd"/>
<constraint firstAttribute="trailing" secondItem="hRR-84-Owd" secondAttribute="trailing" id="lbU-TC-jhJ"/>
<constraint firstAttribute="bottom" secondItem="hRR-84-Owd" secondAttribute="bottom" id="nOI-Qj-lbm"/>
</constraints>
</view>
</subviews>
</view>
<constraints>
<constraint firstAttribute="trailing" secondItem="w1r-LJ-TDs" secondAttribute="trailing" constant="15" id="4ns-Zq-D4j"/>
<constraint firstItem="w1r-LJ-TDs" firstAttribute="leading" secondItem="ln4-pC-7KY" secondAttribute="leading" constant="15" id="G1K-up-08u"/>
<constraint firstAttribute="bottom" secondItem="w1r-LJ-TDs" secondAttribute="bottom" constant="20" id="Kk0-dF-4OW"/>
<constraint firstItem="Y3g-Md-6xH" firstAttribute="top" secondItem="ln4-pC-7KY" secondAttribute="top" constant="20" id="PRR-aX-AiM"/>
<constraint firstAttribute="trailing" secondItem="Y3g-Md-6xH" secondAttribute="trailing" constant="20" id="g1Q-lg-I9O"/>
<constraint firstItem="w1r-LJ-TDs" firstAttribute="top" secondItem="Y3g-Md-6xH" secondAttribute="bottom" constant="15" id="i9W-bl-J9R"/>
<constraint firstItem="Y3g-Md-6xH" firstAttribute="leading" secondItem="ln4-pC-7KY" secondAttribute="leading" constant="20" id="j6L-IY-ALs"/>
</constraints>
<viewLayoutGuide key="safeArea" id="btu-iP-81i"/>
<connections>
<outlet property="actionButton" destination="DeC-Y2-fvR" id="VDk-4D-STy"/>
<outlet property="appIconImageView" destination="F2j-pX-09A" id="COe-74-adn"/>
<outlet property="betaBadgeView" destination="5gN-I2-QOB" id="hu7-Ax-Wbc"/>
<outlet property="developerLabel" destination="B5S-HI-tWJ" id="QGh-1g-fFv"/>
<outlet property="nameLabel" destination="xni-8I-ewW" id="V56-ZT-vFa"/>
<outlet property="screenshotsCollectionView" destination="RFs-qp-Ca4" id="xfi-AN-l17"/>
<outlet property="screenshotsContentView" destination="w1r-LJ-TDs" id="iWJ-52-rbA"/>
<outlet property="subtitleLabel" destination="Imx-Le-bcy" id="JVW-ZZ-51O"/>
</connections>
</collectionViewCell>
</objects>
<resources>
<image name="BetaBadge" width="41" height="17"/>
</resources>
</document>

View File

@@ -7,39 +7,152 @@
//
import UIKit
import Combine
import AltStoreCore
import Roxas
import Nuke
import Minimuxer
class BrowseViewController: UICollectionViewController
class BrowseViewController: UICollectionViewController, PeekPopPreviewing
{
private lazy var dataSource = self.makeDataSource()
private lazy var placeholderView = RSTPlaceholderView(frame: .zero)
// Nil == Show apps from all sources.
let source: Source?
private let prototypeCell = BrowseCollectionViewCell.instantiate(with: BrowseCollectionViewCell.nib!)!
private var loadingState: LoadingState = .loading {
private(set) var category: StoreCategory? {
didSet {
self.updateDataSource()
self.update()
}
}
var searchPredicate: NSPredicate? {
didSet {
self.updateDataSource()
}
}
private lazy var dataSource = self.makeDataSource()
private lazy var placeholderView = RSTPlaceholderView(frame: .zero)
private let prototypeCell = AppCardCollectionViewCell(frame: .zero)
private var sortButton: UIBarButtonItem?
private var preferredAppSorting: AppSorting = UserDefaults.shared.preferredAppSorting
private var cancellables = Set<AnyCancellable>()
private var titleStackView: UIStackView!
private var titleSourceIconView: AppIconImageView!
private var titleCategoryIconView: UIImageView!
private var titleLabel: UILabel!
init?(source: Source?, coder: NSCoder)
{
self.source = source
self.category = nil
super.init(coder: coder)
}
init?(category: StoreCategory?, coder: NSCoder)
{
self.source = nil
self.category = category
super.init(coder: coder)
}
required init?(coder: NSCoder)
{
self.source = nil
self.category = nil
super.init(coder: coder)
}
private var cachedItemSizes = [String: CGSize]()
@IBOutlet private var sourcesBarButtonItem: UIBarButtonItem!
override func viewDidLoad()
{
super.viewDidLoad()
self.collectionView.backgroundColor = .altBackground
self.collectionView.alwaysBounceVertical = true
self.dataSource.searchController.searchableKeyPaths = [#keyPath(StoreApp.name),
#keyPath(StoreApp.subtitle),
#keyPath(StoreApp.developerName),
#keyPath(StoreApp.bundleIdentifier)]
self.navigationItem.searchController = self.dataSource.searchController
self.prototypeCell.contentView.translatesAutoresizingMaskIntoConstraints = false
self.collectionView.register(BrowseCollectionViewCell.nib, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier)
self.collectionView.register(AppCardCollectionViewCell.self, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier)
self.collectionView.dataSource = self.dataSource
self.collectionView.prefetchDataSource = self.dataSource
self.registerForPreviewing(with: self, sourceView: self.collectionView)
let collectionViewLayout = self.collectionViewLayout as! UICollectionViewFlowLayout
collectionViewLayout.minimumLineSpacing = 30
(self as PeekPopPreviewing).registerForPreviewing(with: self, sourceView: self.collectionView)
let refreshControl = UIRefreshControl(frame: .zero, primaryAction: UIAction { [weak self] _ in
self?.updateSources()
})
self.collectionView.refreshControl = refreshControl
if self.category != nil, #available(iOS 16, *)
{
let categoriesMenu = UIMenu(children: [
UIDeferredMenuElement.uncached { [weak self] completion in
let actions = self?.makeCategoryActions() ?? []
completion(actions)
}
])
self.navigationItem.titleMenuProvider = { _ in categoriesMenu }
}
self.titleSourceIconView = AppIconImageView(style: .circular)
self.titleCategoryIconView = UIImageView(frame: .zero)
self.titleCategoryIconView.contentMode = .scaleAspectFit
self.titleLabel = UILabel()
self.titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
self.titleStackView = UIStackView(arrangedSubviews: [self.titleSourceIconView, self.titleCategoryIconView, self.titleLabel])
self.titleStackView.spacing = 4
self.titleStackView.translatesAutoresizingMaskIntoConstraints = false
self.navigationItem.largeTitleDisplayMode = .never
if #available(iOS 16, *)
{
self.navigationItem.preferredSearchBarPlacement = .automatic
}
if #available(iOS 15, *)
{
self.prepareAppSorting()
}
self.preparePipeline()
NSLayoutConstraint.activate([
// Source icon = equal width and height
self.titleSourceIconView.heightAnchor.constraint(equalToConstant: 26),
self.titleSourceIconView.widthAnchor.constraint(equalTo: self.titleSourceIconView.heightAnchor),
// Category icon = constant height, variable widths
self.titleCategoryIconView.heightAnchor.constraint(equalToConstant: 26)
])
self.updateDataSource()
self.update()
}
@@ -47,176 +160,371 @@ class BrowseViewController: UICollectionViewController
{
super.viewWillAppear(animated)
self.fetchSource()
self.updateDataSource()
self.update()
}
override func viewDidDisappear(_ animated: Bool)
{
super.viewDidDisappear(animated)
self.navigationController?.navigationBar.tintColor = nil
}
}
private extension BrowseViewController
{
func makeDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<StoreApp, UIImage>
func preparePipeline()
{
AppManager.shared.$updateSourcesResult
.receive(on: RunLoop.main) // Delay to next run loop so we receive _current_ value (not previous value).
.sink { [weak self] result in
self?.update()
}
.store(in: &self.cancellables)
}
func makeFetchRequest() -> NSFetchRequest<StoreApp>
{
let fetchRequest = StoreApp.fetchRequest() as NSFetchRequest<StoreApp>
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \StoreApp.sortIndex, ascending: true), NSSortDescriptor(keyPath: \StoreApp.name, ascending: true)]
fetchRequest.returnsObjectsAsFaults = false
if let source = Source.fetchAltStoreSource(in: DatabaseManager.shared.viewContext)
let predicate = StoreApp.visibleAppsPredicate
if let source = self.source
{
fetchRequest.predicate = NSPredicate(format: "%K != %@ AND %K == %@", #keyPath(StoreApp.bundleIdentifier), StoreApp.altstoreAppID, #keyPath(StoreApp.source), source)
let filterPredicate = NSPredicate(format: "%K == %@", #keyPath(StoreApp._source), source)
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [filterPredicate, predicate])
}
else if let category = self.category
{
let categoryPredicate = switch category {
case .other: StoreApp.otherCategoryPredicate
default: NSPredicate(format: "%K == %@", #keyPath(StoreApp._category), category.rawValue)
}
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [categoryPredicate, predicate])
}
else
{
fetchRequest.predicate = NSPredicate(format: "%K != %@", #keyPath(StoreApp.bundleIdentifier), StoreApp.altstoreAppID)
fetchRequest.predicate = predicate
}
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<StoreApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
dataSource.cellConfigurationHandler = { (cell, app, indexPath) in
let cell = cell as! BrowseCollectionViewCell
cell.nameLabel.text = app.name
cell.developerLabel.text = app.developerName
cell.subtitleLabel.text = app.subtitle
cell.imageURLs = Array(app.screenshotURLs.prefix(2))
cell.appIconImageView.image = nil
cell.appIconImageView.isIndicatingActivity = true
cell.betaBadgeView.isHidden = !app.isBeta
var sortDescriptors = [NSSortDescriptor(keyPath: \StoreApp.name, ascending: true),
NSSortDescriptor(keyPath: \StoreApp.bundleIdentifier, ascending: true),
NSSortDescriptor(keyPath: \StoreApp.sourceIdentifier, ascending: true)]
switch self.preferredAppSorting
{
case .default:
let descriptor = NSSortDescriptor(keyPath: \StoreApp.sortIndex, ascending: self.preferredAppSorting.isAscending)
sortDescriptors.insert(descriptor, at: 0)
cell.actionButton.addTarget(self, action: #selector(BrowseViewController.performAppAction(_:)), for: .primaryActionTriggered)
cell.actionButton.activityIndicatorView.style = .white
case .name:
// Already sorting by name, no need to prepend additional sort descriptor.
break
// Explicitly set to false to ensure we're starting from a non-activity indicating state.
// Otherwise, cell reuse can mess up some cached values.
cell.actionButton.isIndicatingActivity = false
case .developer:
let descriptor = NSSortDescriptor(keyPath: \StoreApp.developerName, ascending: self.preferredAppSorting.isAscending)
sortDescriptors.insert(descriptor, at: 0)
case .lastUpdated:
let descriptor = NSSortDescriptor(keyPath: \StoreApp.latestSupportedVersion?.date, ascending: self.preferredAppSorting.isAscending)
sortDescriptors.insert(descriptor, at: 0)
}
fetchRequest.sortDescriptors = sortDescriptors
return fetchRequest
}
func makeDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<StoreApp, UIImage>
{
let fetchRequest = self.makeFetchRequest()
let context = self.source?.managedObjectContext ?? DatabaseManager.shared.viewContext
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<StoreApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: context)
dataSource.placeholderView = self.placeholderView
dataSource.cellConfigurationHandler = { [weak self] (cell, app, indexPath) in
guard let self else { return }
let cell = cell as! AppCardCollectionViewCell
cell.layoutMargins.left = self.view.layoutMargins.left
cell.layoutMargins.right = self.view.layoutMargins.right
let showSourceIcon = (self.source == nil) // Hide source icon if redundant
cell.configure(for: app, showSourceIcon: showSourceIcon)
cell.bannerView.iconImageView.image = nil
cell.bannerView.iconImageView.isIndicatingActivity = true
cell.bannerView.button.addTarget(self, action: #selector(BrowseViewController.performAppAction(_:)), for: .primaryActionTriggered)
cell.bannerView.button.activityIndicatorView.style = .medium
cell.bannerView.button.activityIndicatorView.color = .white
let tintColor = app.tintColor ?? .altPrimary
cell.tintColor = tintColor
if app.installedApp == nil
{
cell.actionButton.setTitle(NSLocalizedString("FREE", comment: ""), for: .normal)
let progress = AppManager.shared.installationProgress(for: app)
cell.actionButton.progress = progress
cell.actionButton.isInverted = false
if Date() < app.versionDate
{
cell.actionButton.countdownDate = app.versionDate
}
else
{
cell.actionButton.countdownDate = nil
}
}
else
{
cell.actionButton.setTitle(NSLocalizedString("OPEN", comment: ""), for: .normal)
cell.actionButton.progress = nil
cell.actionButton.isInverted = true
cell.actionButton.countdownDate = nil
}
}
dataSource.prefetchHandler = { (storeApp, indexPath, completionHandler) -> Foundation.Operation? in
let iconURL = storeApp.iconURL
return RSTAsyncBlockOperation() { (operation) in
ImagePipeline.shared.loadImage(with: iconURL, progress: nil, completion: { (response, error) in
ImagePipeline.shared.loadImage(with: iconURL, progress: nil) { result in
guard !operation.isCancelled else { return operation.finish() }
if let image = response?.image
switch result
{
completionHandler(image, nil)
case .success(let response): completionHandler(response.image, nil)
case .failure(let error): completionHandler(nil, error)
}
else
{
completionHandler(nil, error)
}
})
}
}
}
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
let cell = cell as! BrowseCollectionViewCell
cell.appIconImageView.isIndicatingActivity = false
cell.appIconImageView.image = image
dataSource.prefetchCompletionHandler = { [weak dataSource] (cell, image, indexPath, error) in
let cell = cell as! AppCardCollectionViewCell
cell.bannerView.iconImageView.isIndicatingActivity = false
cell.bannerView.iconImageView.image = image
if let error = error
if let error = error, let dataSource
{
print("Error loading image:", error)
let app = dataSource.item(at: indexPath)
Logger.main.debug("Failed to load app icon from \(app.iconURL, privacy: .public). \(error.localizedDescription, privacy: .public)")
}
}
dataSource.placeholderView = self.placeholderView
return dataSource
}
func updateDataSource()
{
if let patreonAccount = DatabaseManager.shared.patreonAccount(), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated
{
self.dataSource.predicate = nil
}
else
{
self.dataSource.predicate = NSPredicate(format: "%K == NO", #keyPath(StoreApp.isBeta))
}
let fetchRequest = self.makeFetchRequest()
let context = self.source?.managedObjectContext ?? DatabaseManager.shared.viewContext
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
self.dataSource.fetchedResultsController = fetchedResultsController
self.dataSource.predicate = self.searchPredicate
}
func fetchSource()
func updateSources()
{
self.loadingState = .loading
AppManager.shared.fetchSource() { (result) in
do
AppManager.shared.updateAllSources { result in
self.collectionView.refreshControl?.endRefreshing()
guard case .failure(let error) = result else { return }
if self.dataSource.itemCount > 0
{
let source = try result.get()
try source.managedObjectContext?.save()
DispatchQueue.main.async {
self.loadingState = .finished(.success(()))
}
}
catch
{
DispatchQueue.main.async {
if self.dataSource.itemCount > 0
{
let toastView = ToastView(text: error.localizedDescription, detailText: nil)
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0)
}
self.loadingState = .finished(.failure(error))
}
let toastView = ToastView(error: error)
toastView.addTarget(nil, action: #selector(TabBarController.presentSources), for: .touchUpInside)
toastView.show(in: self)
}
}
}
func update()
{
switch self.loadingState
if self.searchPredicate != nil
{
case .loading:
self.placeholderView.textLabel.isHidden = true
self.placeholderView.detailTextLabel.isHidden = false
self.placeholderView.detailTextLabel.text = NSLocalizedString("Loading...", comment: "")
self.placeholderView.activityIndicatorView.startAnimating()
case .finished(.failure(let error)):
self.placeholderView.textLabel.text = NSLocalizedString("No Apps", comment: "")
self.placeholderView.textLabel.isHidden = false
self.placeholderView.detailTextLabel.text = NSLocalizedString("Please make sure your spelling is correct, or try searching for another app.", comment: "")
self.placeholderView.detailTextLabel.isHidden = false
self.placeholderView.textLabel.text = NSLocalizedString("Unable to Fetch Apps", comment: "")
self.placeholderView.detailTextLabel.text = error.localizedDescription
self.placeholderView.activityIndicatorView.stopAnimating()
case .finished(.success):
self.placeholderView.textLabel.isHidden = true
self.placeholderView.detailTextLabel.isHidden = true
self.placeholderView.activityIndicatorView.stopAnimating()
}
else
{
switch AppManager.shared.updateSourcesResult
{
case nil:
self.placeholderView.textLabel.isHidden = true
self.placeholderView.detailTextLabel.isHidden = false
self.placeholderView.detailTextLabel.text = NSLocalizedString("Loading...", comment: "")
self.placeholderView.activityIndicatorView.startAnimating()
case .failure(let error):
self.placeholderView.textLabel.isHidden = false
self.placeholderView.detailTextLabel.isHidden = false
self.placeholderView.textLabel.text = NSLocalizedString("Unable to Fetch Apps", comment: "")
self.placeholderView.detailTextLabel.text = error.localizedDescription
self.placeholderView.activityIndicatorView.stopAnimating()
case .success:
self.placeholderView.textLabel.text = NSLocalizedString("No Apps", comment: "")
self.placeholderView.textLabel.isHidden = false
self.placeholderView.detailTextLabel.isHidden = true
self.placeholderView.activityIndicatorView.stopAnimating()
}
}
let tintColor: UIColor
if let source = self.source
{
tintColor = source.effectiveTintColor?.adjustedForDisplay ?? .altPrimary
self.title = source.name
self.titleSourceIconView.backgroundColor = tintColor
self.titleSourceIconView.isHidden = false
self.titleCategoryIconView.isHidden = true
if let iconURL = source.effectiveIconURL
{
Nuke.loadImage(with: iconURL, into: self.titleSourceIconView) { result in
switch result
{
case .failure(let error): Logger.main.error("Failed to fetch source icon at \(iconURL, privacy: .public). \(error.localizedDescription, privacy: .public)")
case .success: self.titleSourceIconView.backgroundColor = .white
}
}
}
}
else if let category = self.category
{
tintColor = category.tintColor
self.title = category.localizedName
let image = UIImage(systemName: category.filledSymbolName)?.withTintColor(tintColor, renderingMode: .alwaysOriginal)
self.titleCategoryIconView.image = image
self.titleCategoryIconView.isHidden = false
self.titleSourceIconView.isHidden = true
}
else
{
tintColor = .altPrimary
self.title = NSLocalizedString("Browse", comment: "")
self.titleSourceIconView.isHidden = true
self.titleCategoryIconView.isHidden = true
}
self.titleLabel.text = self.title
self.titleStackView.sizeToFit()
self.navigationItem.titleView = self.titleStackView
self.view.tintColor = tintColor
let appearance = NavigationBarAppearance()
appearance.configureWithTintColor(tintColor)
appearance.configureWithDefaultBackground()
let edgeAppearance = appearance.copy()
edgeAppearance.configureWithTransparentBackground()
self.navigationItem.standardAppearance = appearance
self.navigationItem.scrollEdgeAppearance = edgeAppearance
// Necessary to tint UISearchController's inline bar button.
self.navigationController?.navigationBar.tintColor = tintColor
if let sortButton
{
sortButton.image = sortButton.image?.withTintColor(tintColor, renderingMode: .alwaysOriginal)
}
}
func makeCategoryActions() -> [UIAction]
{
let handler = { [weak self] (category: StoreCategory) in
self?.category = category
}
let fetchRequest = NSFetchRequest(entityName: StoreApp.entity().name!) as NSFetchRequest<NSDictionary>
fetchRequest.resultType = .dictionaryResultType
fetchRequest.returnsDistinctResults = true
fetchRequest.propertiesToFetch = [#keyPath(StoreApp._category)]
fetchRequest.predicate = StoreApp.visibleAppsPredicate
do
{
let dictionaries = try DatabaseManager.shared.viewContext.fetch(fetchRequest)
// Keep nil values
let categories = dictionaries.map { $0[#keyPath(StoreApp._category)] as? String? ?? nil }.map { rawCategory -> StoreCategory in
guard let rawCategory else { return .other }
return StoreCategory(rawValue: rawCategory) ?? .other
}
var sortedCategories = Set(categories).sorted(by: { $0.localizedName.localizedStandardCompare($1.localizedName) == .orderedAscending })
if let otherIndex = sortedCategories.firstIndex(of: .other)
{
// Ensure "Other" is always last
sortedCategories.move(fromOffsets: [otherIndex], toOffset: sortedCategories.count)
}
let actions = sortedCategories.map { category in
let state: UIAction.State = (category == self.category) ? .on : .off
let image = UIImage(systemName: category.filledSymbolName)?.withTintColor(category.tintColor, renderingMode: .alwaysOriginal)
return UIAction(title: category.localizedName, image: image, state: state) { _ in
handler(category)
}
}
return actions
}
catch
{
Logger.main.error("Failed to fetch categories. \(error.localizedDescription, privacy: .public)")
return []
}
}
@available(iOS 15, *)
func prepareAppSorting()
{
if self.preferredAppSorting == .default && self.source == nil
{
// Only allow `default` sorting if source is non-nil.
// Otherwise, fall back to `lastUpdated` sorting.
self.preferredAppSorting = .lastUpdated
// Don't update UserDefaults unless explicitly changed by user.
// UserDefaults.shared.preferredAppSorting = .lastUpdated
}
let children = UIDeferredMenuElement.uncached { [weak self] completion in
guard let self else { return completion([]) }
var sortingOptions = AppSorting.allCases
if self.source == nil
{
// Only allow `default` sorting when source is non-nil.
sortingOptions = sortingOptions.filter { $0 != .default }
}
let actions = sortingOptions.map { sorting in
let state: UIMenuElement.State = (sorting == self.preferredAppSorting) ? .on : .off
let action = UIAction(title: sorting.localizedName, image: nil, state: state) { action in
self.preferredAppSorting = sorting
UserDefaults.shared.preferredAppSorting = sorting // Update separately to save change.
self.updateDataSource()
}
return action
}
completion(actions)
}
let sortMenu = UIMenu(title: NSLocalizedString("Sort by…", comment: ""), options: [.singleSelection], children: [children])
let sortIcon = UIImage(systemName: "arrow.up.arrow.down")
let sortButton = UIBarButtonItem(title: NSLocalizedString("Sort by…", comment: ""), image: sortIcon, primaryAction: nil, menu: sortMenu)
self.sortButton = sortButton
self.navigationItem.rightBarButtonItems = [sortButton]
}
}
@@ -229,7 +537,8 @@ private extension BrowseViewController
let app = self.dataSource.item(at: indexPath)
if let installedApp = app.installedApp
// if let installedApp = app.installedApp, !installedApp.isUpdateAvailable
if let installedApp = app.installedApp, !installedApp.hasUpdate
{
self.open(installedApp)
}
@@ -246,24 +555,55 @@ private extension BrowseViewController
previousProgress?.cancel()
return
}
if !isMinimuxerReady {
let toastView = ToastView(error: MinimuxerError.NoConnection)
toastView.show(in: self)
return
}
_ = AppManager.shared.install(app, presentingViewController: self) { (result) in
Task<Void, Never>(priority: .userInitiated) { @MainActor in
// if let installedApp = app.installedApp, installedApp.isUpdateAvailable
if let installedApp = app.installedApp, installedApp.hasUpdate
{
AppManager.shared.update(installedApp, presentingViewController: self, completionHandler: finish(_:))
}
else
{
await AppManager.shared.installAsync(app, presentingViewController: self, completionHandler: finish(_:))
}
UIView.performWithoutAnimation {
self.collectionView.reloadItems(at: [indexPath])
}
}
@MainActor
func finish(_ result: Result<InstalledApp, Error>)
{
DispatchQueue.main.async {
switch result
{
case .failure(OperationError.cancelled): break // Ignore
case .failure(let error):
let toastView = ToastView(text: error.localizedDescription, detailText: nil)
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2)
let toastView = ToastView(error: error, opensLog: true)
toastView.show(in: self)
case .success: print("Installed app:", app.bundleIdentifier)
}
self.collectionView.reloadItems(at: [indexPath])
UIView.performWithoutAnimation {
if let indexPath = self.dataSource.fetchedResultsController.indexPath(forObject: app)
{
self.collectionView.reloadItems(at: [indexPath])
}
else
{
self.collectionView.reloadSections(IndexSet(integer: indexPath.section))
}
}
}
}
self.collectionView.reloadItems(at: [indexPath])
}
func open(_ installedApp: InstalledApp)
@@ -277,50 +617,44 @@ extension BrowseViewController: UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
let item = self.dataSource.item(at: indexPath)
let itemID = item.globallyUniqueID ?? item.bundleIdentifier
if let previousSize = self.cachedItemSizes[item.bundleIdentifier]
if let previousSize = self.cachedItemSizes[itemID]
{
return previousSize
}
let maxVisibleScreenshots = 2 as CGFloat
let aspectRatio: CGFloat = 16.0 / 9.0
let layout = collectionViewLayout as! UICollectionViewFlowLayout
let padding = (layout.minimumInteritemSpacing * (maxVisibleScreenshots - 1))
self.dataSource.cellConfigurationHandler(self.prototypeCell, item, indexPath)
let insets = (self.view.layoutMargins.left + self.view.layoutMargins.right)
let widthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionView.bounds.width)
let widthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionView.bounds.width - insets)
widthConstraint.isActive = true
defer { widthConstraint.isActive = false }
// Manually update cell width & layout so we can accurately calculate screenshot sizes.
self.prototypeCell.frame.size.width = widthConstraint.constant
self.prototypeCell.layoutIfNeeded()
let collectionViewWidth = self.prototypeCell.screenshotsCollectionView.bounds.width
let screenshotWidth = ((collectionViewWidth - padding) / maxVisibleScreenshots).rounded(.down)
let screenshotHeight = screenshotWidth * aspectRatio
let heightConstraint = self.prototypeCell.screenshotsCollectionView.heightAnchor.constraint(equalToConstant: screenshotHeight)
heightConstraint.isActive = true
defer { heightConstraint.isActive = false }
let itemSize = self.prototypeCell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
self.cachedItemSizes[item.bundleIdentifier] = itemSize
self.cachedItemSizes[itemID] = itemSize
return itemSize
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
let app = self.dataSource.item(at: indexPath)
let appViewController = AppViewController.makeAppViewController(app: app)
self.navigationController?.pushViewController(appViewController, animated: true)
// Fall back to presentingViewController.navigationController in case we're being used for search results.
let navigationController = self.navigationController ?? self.presentingViewController?.navigationController
navigationController?.pushViewController(appViewController, animated: true)
}
}
extension BrowseViewController: UIViewControllerPreviewingDelegate
{
@available(iOS, deprecated: 13.0)
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController?
{
guard
@@ -336,8 +670,22 @@ extension BrowseViewController: UIViewControllerPreviewingDelegate
return appViewController
}
@available(iOS, deprecated: 13.0)
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController)
{
self.navigationController?.pushViewController(viewControllerToCommit, animated: true)
}
}
@available(iOS 17, *)
#Preview(traits: .portrait) {
DatabaseManager.shared.startForPreview()
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let browseViewController = storyboard.instantiateViewController(identifier: "browseViewController") { coder in
BrowseViewController(source: nil, coder: coder)
}
let navigationController = UINavigationController(rootViewController: browseViewController)
return navigationController
}

View File

@@ -0,0 +1,100 @@
//
// FeaturedComponents.swift
// AltStore
//
// Created by Riley Testut on 12/4/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import UIKit
class LargeIconCollectionViewCell: UICollectionViewCell
{
let textLabel = UILabel(frame: .zero)
let imageView = UIImageView(frame: .zero)
override init(frame: CGRect)
{
self.textLabel.translatesAutoresizingMaskIntoConstraints = false
self.textLabel.textColor = .white
self.textLabel.font = .preferredFont(forTextStyle: .headline)
self.imageView.translatesAutoresizingMaskIntoConstraints = false
self.imageView.contentMode = .center
self.imageView.tintColor = .white
self.imageView.alpha = 0.4
self.imageView.preferredSymbolConfiguration = .init(pointSize: 80)
super.init(frame: frame)
self.contentView.clipsToBounds = true
self.contentView.layer.cornerRadius = 16
self.contentView.layer.cornerCurve = .continuous
self.contentView.addSubview(self.textLabel)
self.contentView.addSubview(self.imageView)
NSLayoutConstraint.activate([
self.textLabel.leadingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.leadingAnchor, constant: 4),
self.textLabel.bottomAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.bottomAnchor, constant: -4),
self.imageView.centerXAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -30),
self.imageView.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor, constant: 0),
self.imageView.heightAnchor.constraint(equalTo: self.contentView.heightAnchor, constant: 0),
self.imageView.widthAnchor.constraint(equalTo: self.imageView.heightAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class IconButtonCollectionReusableView: UICollectionReusableView
{
let iconButton: UIButton
let titleButton: UIButton
private let stackView: UIStackView
override init(frame: CGRect)
{
let iconHeight = 26.0
self.iconButton = UIButton(type: .custom)
self.iconButton.translatesAutoresizingMaskIntoConstraints = false
self.iconButton.clipsToBounds = true
self.iconButton.layer.cornerRadius = iconHeight / 2
let content = UIListContentConfiguration.plainHeader()
self.titleButton = UIButton(type: .system)
self.titleButton.translatesAutoresizingMaskIntoConstraints = false
self.titleButton.titleLabel?.font = content.textProperties.font
self.titleButton.setTitleColor(content.textProperties.color, for: .normal)
self.stackView = UIStackView(arrangedSubviews: [self.iconButton, self.titleButton])
self.stackView.translatesAutoresizingMaskIntoConstraints = false
self.stackView.axis = .horizontal
self.stackView.alignment = .center
self.stackView.spacing = UIStackView.spacingUseSystem
self.stackView.isLayoutMarginsRelativeArrangement = false
super.init(frame: frame)
self.addSubview(self.stackView)
NSLayoutConstraint.activate([
self.iconButton.heightAnchor.constraint(equalToConstant: iconHeight),
self.iconButton.widthAnchor.constraint(equalTo: self.iconButton.heightAnchor),
self.stackView.topAnchor.constraint(equalTo: self.topAnchor),
self.stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
self.stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@@ -0,0 +1,746 @@
//
// FeaturedViewController.swift
// AltStore
//
// Created by Riley Testut on 11/8/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import UIKit
import AltStoreCore
import Roxas
import Nuke
extension UIAction.Identifier
{
fileprivate static let showAllApps = Self("io.sidestore.ShowAllApps")
fileprivate static let showSourceDetails = Self("io.sidestore.ShowSourceDetails")
}
extension FeaturedViewController
{
// Open-ended because each Source is its own section
private struct Section: RawRepresentable, Equatable
{
static let recentlyUpdated = Section(rawValue: 0)
static let categories = Section(rawValue: 1)
static let featuredHeader = Section(rawValue: 2)
let rawValue: Int
var isFeaturedAppsSection: Bool {
return self.rawValue > Section.featuredHeader.rawValue
}
init(rawValue: Int)
{
self.rawValue = rawValue
}
}
private enum ReuseID: String
{
case recent = "RecentCell"
case category = "CategoryCell"
case featuredApp = "FeaturedAppCell"
}
private enum ElementKind: String
{
case sectionHeader
case sourceHeader
case button
}
}
class FeaturedViewController: UICollectionViewController
{
private lazy var dataSource = self.makeDataSource()
private lazy var recentlyUpdatedDataSource = self.makeRecentlyUpdatedDataSource()
private lazy var categoriesDataSource = self.makeCategoriesDataSource()
private lazy var featuredAppsDataSource = self.makeFeaturedAppsDataSource()
private var searchController: RSTSearchController!
private var searchBrowseViewController: BrowseViewController!
override func viewDidLoad()
{
super.viewDidLoad()
self.title = NSLocalizedString("Browse", comment: "")
let layout = Self.makeLayout()
self.collectionView.collectionViewLayout = layout
self.dataSource.proxy = self
self.collectionView.dataSource = self.dataSource
self.collectionView.prefetchDataSource = self.dataSource
self.collectionView.register(AppBannerCollectionViewCell.self, forCellWithReuseIdentifier: ReuseID.recent.rawValue)
self.collectionView.register(LargeIconCollectionViewCell.self, forCellWithReuseIdentifier: ReuseID.category.rawValue)
self.collectionView.register(AppCardCollectionViewCell.self, forCellWithReuseIdentifier: ReuseID.featuredApp.rawValue)
self.collectionView.register(UICollectionViewListCell.self, forSupplementaryViewOfKind: ElementKind.sectionHeader.rawValue, withReuseIdentifier: ElementKind.sectionHeader.rawValue)
self.collectionView.register(IconButtonCollectionReusableView.self, forSupplementaryViewOfKind: ElementKind.sourceHeader.rawValue, withReuseIdentifier: ElementKind.sourceHeader.rawValue)
self.collectionView.register(ButtonCollectionReusableView.self, forSupplementaryViewOfKind: ElementKind.button.rawValue, withReuseIdentifier: ElementKind.button.rawValue)
self.collectionView.backgroundColor = .altBackground
self.collectionView.directionalLayoutMargins.leading = 20
self.collectionView.directionalLayoutMargins.trailing = 20
let storyboard = UIStoryboard(name: "Main", bundle: nil)
self.searchBrowseViewController = storyboard.instantiateViewController(identifier: "browseViewController") { coder in
let browseViewController = BrowseViewController(coder: coder)
return browseViewController
}
self.searchController = RSTSearchController(searchResultsController: self.searchBrowseViewController)
self.searchController.searchableKeyPaths = [#keyPath(StoreApp.name),
#keyPath(StoreApp.developerName),
#keyPath(StoreApp.subtitle),
#keyPath(StoreApp.bundleIdentifier)]
self.searchController.searchHandler = { [weak searchBrowseViewController] (searchValue, _) in
searchBrowseViewController?.searchPredicate = searchValue.predicate
return nil
}
self.navigationItem.searchController = self.searchController
self.navigationItem.hidesSearchBarWhenScrolling = true
self.navigationItem.largeTitleDisplayMode = .always
}
override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)
self.navigationController?.navigationBar.tintColor = .altPrimary
}
}
private extension FeaturedViewController
{
class func makeLayout() -> UICollectionViewCompositionalLayout
{
let config = UICollectionViewCompositionalLayoutConfiguration()
config.interSectionSpacing = 0 // Must be 0 for Section.featuredHeader
config.contentInsetsReference = .layoutMargins
let layout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
let section = Section(rawValue: sectionIndex)
let spacing = 10.0
let interSectionSpacing = 30.0
let titleSize = NSCollectionLayoutSize(widthDimension: .estimated(100), heightDimension: .estimated(30))
switch section
{
case .recentlyUpdated:
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(AppBannerView.standardHeight))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(AppBannerView.standardHeight * 2 + spacing))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item, item]) // 2 items per group
group.interItemSpacing = .fixed(spacing)
let layoutSection = NSCollectionLayoutSection(group: group)
layoutSection.interGroupSpacing = spacing
layoutSection.orthogonalScrollingBehavior = .groupPagingCentered
layoutSection.contentInsets.bottom = interSectionSpacing
layoutSection.boundarySupplementaryItems = [
NSCollectionLayoutBoundarySupplementaryItem(layoutSize: titleSize, elementKind: ElementKind.sectionHeader.rawValue, alignment: .topLeading)
]
return layoutSection
case .categories:
let itemWidth = (layoutEnvironment.container.effectiveContentSize.width - spacing) / 2
let itemHeight = 90.0
let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(itemWidth), heightDimension: .absolute(itemHeight))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(itemHeight))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item, item]) // 2 items per group
group.interItemSpacing = .fixed(spacing)
let layoutSection = NSCollectionLayoutSection(group: group)
layoutSection.interGroupSpacing = spacing
layoutSection.orthogonalScrollingBehavior = .none
layoutSection.contentInsets.bottom = interSectionSpacing
layoutSection.boundarySupplementaryItems = [
NSCollectionLayoutBoundarySupplementaryItem(layoutSize: titleSize, elementKind: ElementKind.sectionHeader.rawValue, alignment: .topLeading)
]
return layoutSection
case .featuredHeader:
// We don't want to show any items, so set height to 1.0
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let group = NSCollectionLayoutGroup.vertical(layoutSize: itemSize, subitems: [item])
let layoutSection = NSCollectionLayoutSection(group: group)
layoutSection.contentInsets.top = 0
layoutSection.contentInsets.bottom = 0
layoutSection.boundarySupplementaryItems = [
NSCollectionLayoutBoundarySupplementaryItem(layoutSize: titleSize, elementKind: ElementKind.sectionHeader.rawValue, alignment: .topLeading)
]
return layoutSection
case _ where section.isFeaturedAppsSection:
let itemHeight: NSCollectionLayoutDimension = if #available(iOS 17, *) { .uniformAcrossSiblings(estimate: 350) } else { .estimated(350) }
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemHeight)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemHeight)
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
group.interItemSpacing = .fixed(spacing)
let titleHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: titleSize, elementKind: ElementKind.sourceHeader.rawValue, alignment: .topLeading)
let buttonSize = NSCollectionLayoutSize(widthDimension: .estimated(44), heightDimension: .estimated(20))
let buttonHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: buttonSize, elementKind: ElementKind.button.rawValue, alignment: .topTrailing)
let layoutSection = NSCollectionLayoutSection(group: group)
layoutSection.interGroupSpacing = spacing
layoutSection.orthogonalScrollingBehavior = .groupPagingCentered
layoutSection.contentInsets.top = 8
layoutSection.contentInsets.bottom = interSectionSpacing
layoutSection.boundarySupplementaryItems = [titleHeader, buttonHeader]
return layoutSection
default: return nil
}
}, configuration: config)
return layout
}
func makeDataSource() -> RSTCompositeCollectionViewPrefetchingDataSource<StoreApp, UIImage>
{
let featuredHeaderDataSource = RSTDynamicCollectionViewDataSource<StoreApp>()
featuredHeaderDataSource.numberOfSectionsHandler = { 1 }
featuredHeaderDataSource.numberOfItemsHandler = { _ in 0 }
let dataSource = RSTCompositeCollectionViewPrefetchingDataSource<StoreApp, UIImage>(dataSources: [self.recentlyUpdatedDataSource, self.categoriesDataSource, featuredHeaderDataSource, self.featuredAppsDataSource])
dataSource.predicate = StoreApp.visibleAppsPredicate // Ensure we never accidentally show hidden apps
return dataSource
}
func makeRecentlyUpdatedDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<StoreApp, UIImage>
{
let fetchRequest = StoreApp.fetchRequest() as NSFetchRequest<StoreApp>
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.sortDescriptors = [
NSSortDescriptor(keyPath: \StoreApp.latestSupportedVersion?.date, ascending: false),
NSSortDescriptor(keyPath: \StoreApp.name, ascending: true),
NSSortDescriptor(keyPath: \StoreApp.bundleIdentifier, ascending: true),
NSSortDescriptor(keyPath: \StoreApp.sourceIdentifier, ascending: true),
]
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<StoreApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
dataSource.cellIdentifierHandler = { _ in ReuseID.recent.rawValue }
dataSource.liveFetchLimit = 10 // Show 10 most recently updated apps
dataSource.cellConfigurationHandler = { cell, storeApp, indexPath in
let cell = cell as! AppBannerCollectionViewCell
cell.tintColor = storeApp.tintColor
cell.contentView.preservesSuperviewLayoutMargins = false
cell.contentView.layoutMargins = .zero
cell.bannerView.button.isIndicatingActivity = false
cell.bannerView.configure(for: storeApp)
if let versionDate = storeApp.latestSupportedVersion?.date
{
cell.bannerView.subtitleLabel.text = Date().relativeDateString(since: versionDate, dateFormatter: Date.mediumDateFormatter)
}
cell.bannerView.button.addTarget(self, action: #selector(FeaturedViewController.performAppAction), for: .primaryActionTriggered)
cell.bannerView.iconImageView.image = nil
cell.bannerView.iconImageView.isIndicatingActivity = true
}
dataSource.prefetchHandler = { (storeApp, indexPath, completion) -> Foundation.Operation? in
return RSTAsyncBlockOperation { (operation) in
storeApp.managedObjectContext?.perform {
ImagePipeline.shared.loadImage(with: storeApp.iconURL, progress: nil) { result in
guard !operation.isCancelled else { return operation.finish() }
switch result
{
case .success(let response): completion(response.image, nil)
case .failure(let error): completion(nil, error)
}
}
}
}
}
dataSource.prefetchCompletionHandler = { [weak dataSource] (cell, image, indexPath, error) in
let cell = cell as! AppBannerCollectionViewCell
cell.bannerView.iconImageView.image = image
cell.bannerView.iconImageView.isIndicatingActivity = false
if let error, let dataSource
{
let app = dataSource.item(at: indexPath)
Logger.main.debug("Failed to app icon from \(app.iconURL, privacy: .public). \(error.localizedDescription, privacy: .public)")
}
}
return dataSource
}
func makeCategoriesDataSource() -> RSTCompositeCollectionViewDataSource<StoreApp>
{
let knownCategories = StoreCategory.allCases.filter { $0 != .other }.map { $0.rawValue }
let knownFetchRequest = StoreApp.fetchRequest()
knownFetchRequest.predicate = NSPredicate(format: "%K IN %@", #keyPath(StoreApp._category), knownCategories)
knownFetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \StoreApp._category, ascending: true),
NSSortDescriptor(keyPath: \StoreApp.bundleIdentifier, ascending: true),
NSSortDescriptor(keyPath: \StoreApp.sourceIdentifier, ascending: true)]
let unknownFetchRequest = StoreApp.fetchRequest()
unknownFetchRequest.predicate = StoreApp.otherCategoryPredicate
unknownFetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \StoreApp._category, ascending: true),
NSSortDescriptor(keyPath: \StoreApp.bundleIdentifier, ascending: true),
NSSortDescriptor(keyPath: \StoreApp.sourceIdentifier, ascending: true)]
let knownController = NSFetchedResultsController(fetchRequest: knownFetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: #keyPath(StoreApp._category), cacheName: nil)
let knownDataSource = RSTFetchedResultsCollectionViewDataSource<StoreApp>(fetchedResultsController: knownController)
knownDataSource.liveFetchLimit = 1 // One app per category
let unknownController = NSFetchedResultsController(fetchRequest: unknownFetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: nil, cacheName: nil)
let unknownDataSource = RSTFetchedResultsCollectionViewDataSource<StoreApp>(fetchedResultsController: unknownController)
unknownDataSource.liveFetchLimit = 1
// Use composite data source to ensure "Other" category is always last.
let dataSource = RSTCompositeCollectionViewDataSource<StoreApp>(dataSources: [knownDataSource, unknownDataSource])
dataSource.shouldFlattenSections = true // Combine into single section, with one StoreApp per category.
dataSource.cellIdentifierHandler = { _ in ReuseID.category.rawValue }
dataSource.cellConfigurationHandler = { cell, storeApp, indexPath in
let category = storeApp.category ?? .other
let cell = cell as! LargeIconCollectionViewCell
cell.textLabel.text = category.localizedName
cell.imageView.image = UIImage(systemName: category.symbolName)
var background = UIBackgroundConfiguration.clear()
background.backgroundColor = category.tintColor
background.cornerRadius = 16
cell.backgroundConfiguration = background
}
return dataSource
}
func makeFeaturedAppsDataSource() -> RSTCompositeCollectionViewPrefetchingDataSource<StoreApp, UIImage>
{
let fetchRequest = StoreApp.fetchRequest() as NSFetchRequest<StoreApp>
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.sortDescriptors = [
// Sort by Source first to group into sections.
NSSortDescriptor(keyPath: \StoreApp._source?.featuredSortID, ascending: true),
// Show uninstalled apps first.
// Sorting by StoreApp.installedApp crashes because InstalledApp does not respond to compare:
// Instead, sort by StoreApp.installedApp.storeApp.source.sourceIdentifier, which will be either nil OR source ID.
NSSortDescriptor(keyPath: \StoreApp.installedApp?.storeApp?.sourceIdentifier, ascending: true),
// Show featured apps first.
// Sorting by StoreApp.featuringSource crashes because Source does not respond to compare:
// Instead, sort by StoreApp.featuringSource.identifier, which will be either nil OR source ID.
NSSortDescriptor(keyPath: \StoreApp.featuringSource?.identifier, ascending: false),
// Randomize order within sections.
NSSortDescriptor(keyPath: \StoreApp.featuredSortID, ascending: true),
// Sanity check to ensure stable ordering
NSSortDescriptor(keyPath: \StoreApp.bundleIdentifier, ascending: true)
]
let sourceHasRemainingAppsPredicate = NSPredicate(format:
"""
SUBQUERY(%K, $app,
($app.%K != %@) AND ($app.%K == nil)
).@count > 0
""",
#keyPath(StoreApp._source._apps),
#keyPath(StoreApp.bundleIdentifier),
StoreApp.altstoreAppID,
#keyPath(StoreApp.installedApp)
)
let primaryFetchRequest = fetchRequest.copy() as! NSFetchRequest<StoreApp>
primaryFetchRequest.predicate = sourceHasRemainingAppsPredicate
let primaryController = NSFetchedResultsController(fetchRequest: primaryFetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: #keyPath(StoreApp._source.featuredSortID), cacheName: nil)
let primaryDataSource = RSTFetchedResultsCollectionViewDataSource<StoreApp>(fetchedResultsController: primaryController)
primaryDataSource.liveFetchLimit = 5
let secondaryFetchRequest = fetchRequest.copy() as! NSFetchRequest<StoreApp>
secondaryFetchRequest.predicate = NSCompoundPredicate(notPredicateWithSubpredicate: sourceHasRemainingAppsPredicate)
let secondaryController = NSFetchedResultsController(fetchRequest: secondaryFetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: #keyPath(StoreApp._source.featuredSortID), cacheName: nil)
let secondaryDataSource = RSTFetchedResultsCollectionViewDataSource<StoreApp>(fetchedResultsController: secondaryController)
secondaryDataSource.liveFetchLimit = 5
// Ensure sources with no remaining apps always come last.
let dataSource = RSTCompositeCollectionViewPrefetchingDataSource<StoreApp, UIImage>(dataSources: [primaryDataSource, secondaryDataSource])
dataSource.cellIdentifierHandler = { _ in ReuseID.featuredApp.rawValue }
dataSource.cellConfigurationHandler = { cell, storeApp, indexPath in
let cell = cell as! AppCardCollectionViewCell
cell.configure(for: storeApp)
cell.prefersPagingScreenshots = false
cell.bannerView.button.addTarget(self, action: #selector(FeaturedViewController.performAppAction), for: .primaryActionTriggered)
cell.bannerView.sourceIconImageView.isHidden = true
cell.bannerView.iconImageView.image = nil
cell.bannerView.iconImageView.isIndicatingActivity = true
}
dataSource.prefetchHandler = { (storeApp, indexPath, completion) -> Foundation.Operation? in
return RSTAsyncBlockOperation { (operation) in
storeApp.managedObjectContext?.perform {
ImagePipeline.shared.loadImage(with: storeApp.iconURL, progress: nil) { result in
guard !operation.isCancelled else { return operation.finish() }
switch result
{
case .success(let response): completion(response.image, nil)
case .failure(let error): completion(nil, error)
}
}
}
}
}
dataSource.prefetchCompletionHandler = { [weak dataSource] (cell, image, indexPath, error) in
let cell = cell as! AppCardCollectionViewCell
cell.bannerView.iconImageView.image = image
cell.bannerView.iconImageView.isIndicatingActivity = false
if let error = error, let dataSource
{
let app = dataSource.item(at: indexPath)
Logger.main.debug("Failed to app icon from \(app.iconURL, privacy: .public). \(error.localizedDescription, privacy: .public)")
}
}
return dataSource
}
}
private extension FeaturedViewController
{
@IBSegueAction
func makeBrowseViewController(_ coder: NSCoder, sender: Any) -> UIViewController?
{
if let category = sender as? StoreCategory
{
let browseViewController = BrowseViewController(category: category, coder: coder)
return browseViewController
}
else if let source = sender as? Source
{
let browseViewController = BrowseViewController(source: source, coder: coder)
return browseViewController
}
else
{
let browseViewController = BrowseViewController(coder: coder)
return browseViewController
}
}
@IBSegueAction
func makeSourceDetailViewController(_ coder: NSCoder, sender: Any?) -> UIViewController?
{
guard let source = sender as? Source else { return nil }
let sourceDetailViewController = SourceDetailViewController(source: source, coder: coder)
return sourceDetailViewController
}
func showAllApps(for source: Source)
{
self.performSegue(withIdentifier: "showBrowseViewController", sender: source)
}
func showSourceDetails(for source: Source)
{
self.performSegue(withIdentifier: "showSourceDetails", sender: source)
}
}
private extension FeaturedViewController
{
@objc func performAppAction(_ sender: PillButton)
{
let point = self.collectionView.convert(sender.center, from: sender.superview)
guard let indexPath = self.collectionView.indexPathForItem(at: point) else { return }
let storeApp = self.dataSource.item(at: indexPath)
// if let installedApp = storeApp.installedApp, !installedApp.isUpdateAvailable
if let installedApp = storeApp.installedApp, !installedApp.hasUpdate
{
self.open(installedApp)
}
else
{
self.install(storeApp, at: indexPath)
}
}
@objc func install(_ storeApp: StoreApp, at indexPath: IndexPath)
{
let previousProgress = AppManager.shared.installationProgress(for: storeApp)
guard previousProgress == nil else {
previousProgress?.cancel()
return
}
// if let installedApp = storeApp.installedApp, installedApp.isUpdateAvailable
if let installedApp = storeApp.installedApp, installedApp.hasUpdate
{
AppManager.shared.update(installedApp, presentingViewController: self, completionHandler: finish(_:))
}
else
{
AppManager.shared.install(storeApp, presentingViewController: self, completionHandler: finish(_:))
}
UIView.performWithoutAnimation {
self.collectionView.reloadItems(at: [indexPath])
}
func finish(_ result: Result<InstalledApp, Error>)
{
DispatchQueue.main.async {
switch result
{
case .failure(OperationError.cancelled): break // Ignore
case .failure(let error):
let toastView = ToastView(error: error)
toastView.opensErrorLog = true
toastView.show(in: self)
case .success:
Logger.main.info("Installed app \(storeApp.bundleIdentifier, privacy: .public) from FeaturedViewController.")
}
for indexPath in self.collectionView.indexPathsForVisibleItems
{
// Only need to reload if it's still visible.
let item = self.dataSource.item(at: indexPath)
guard item == storeApp else { continue }
UIView.performWithoutAnimation {
self.collectionView.reloadItems(at: [indexPath])
}
}
}
}
}
func open(_ installedApp: InstalledApp)
{
UIApplication.shared.open(installedApp.openAppURL)
}
}
extension FeaturedViewController
{
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
{
let section = Section(rawValue: indexPath.section)
switch kind
{
case ElementKind.sourceHeader.rawValue:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: kind, for: indexPath) as! IconButtonCollectionReusableView
let indexPath = IndexPath(item: 0, section: indexPath.section)
let storeApp = self.dataSource.item(at: indexPath)
var content = UIListContentConfiguration.plainHeader()
content.text = storeApp.source?.name ?? NSLocalizedString("Unknown Source", comment: "")
content.textProperties.numberOfLines = 1
content.directionalLayoutMargins.leading = 0
content.imageToTextPadding = 8
content.imageProperties.reservedLayoutSize = CGSize(width: 26, height: 26)
content.imageProperties.maximumSize = CGSize(width: 26, height: 26)
content.imageProperties.cornerRadius = 13
UIView.performWithoutAnimation {
headerView.titleButton.setTitle(content.text, for: .normal)
headerView.titleButton.layoutIfNeeded()
}
headerView.iconButton.backgroundColor = storeApp.source?.effectiveTintColor?.adjustedForDisplay
headerView.iconButton.setImage(nil, for: .normal)
if let iconURL = storeApp.source?.effectiveIconURL
{
ImagePipeline.shared.loadImage(with: iconURL) { result in
guard case .success(let image) = result else { return }
headerView.iconButton.backgroundColor = .white
headerView.iconButton.setImage(image.image, for: .normal)
}
}
let buttons = [headerView.iconButton, headerView.titleButton]
for button in buttons
{
button.removeAction(identifiedBy: .showSourceDetails, for: .primaryActionTriggered)
if let source = storeApp.source
{
let action = UIAction(identifier: .showSourceDetails) { [weak self] _ in
self?.showSourceDetails(for: source)
}
button.addAction(action, for: .primaryActionTriggered)
}
}
return headerView
case ElementKind.sectionHeader.rawValue:
// Regular section header
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: kind, for: indexPath) as! UICollectionViewListCell
var content: UIListContentConfiguration = if #available(iOS 15, *) {
.prominentInsetGroupedHeader()
}
else {
.groupedHeader()
}
switch section
{
case .recentlyUpdated: content.text = NSLocalizedString("New & Updated", comment: "")
case .categories: content.text = NSLocalizedString("Categories", comment: "")
case .featuredHeader: content.text = NSLocalizedString("Featured", comment: "")
default: break
}
content.directionalLayoutMargins.leading = .zero
content.directionalLayoutMargins.trailing = .zero
headerView.contentConfiguration = content
return headerView
case ElementKind.button.rawValue where section.isFeaturedAppsSection:
let buttonView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: kind, for: indexPath) as! ButtonCollectionReusableView
let indexPath = IndexPath(item: 0, section: indexPath.section)
let storeApp = self.dataSource.item(at: indexPath)
buttonView.tintColor = storeApp.source?.effectiveTintColor?.adjustedForDisplay ?? .altPrimary
buttonView.button.setTitle(NSLocalizedString("See All", comment: ""), for: .normal)
buttonView.button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
buttonView.button.contentEdgeInsets.bottom = 8
buttonView.button.removeAction(identifiedBy: .showAllApps, for: .primaryActionTriggered)
if let source = storeApp.source
{
let action = UIAction(identifier: .showAllApps) { [weak self] _ in
self?.showAllApps(for: source)
}
buttonView.button.addAction(action, for: .primaryActionTriggered)
}
return buttonView
default: return UICollectionReusableView(frame: .zero)
}
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
let storeApp = self.dataSource.item(at: indexPath)
let section = Section(rawValue: indexPath.section)
switch section
{
case _ where section.isFeaturedAppsSection: fallthrough
case .recentlyUpdated:
let appViewController = AppViewController.makeAppViewController(app: storeApp)
self.navigationController?.pushViewController(appViewController, animated: true)
case .categories:
let category = storeApp.category ?? .other
self.performSegue(withIdentifier: "showBrowseViewController", sender: category)
default: break
}
}
}
@available(iOS 17, *)
#Preview(traits: .portrait) {
DatabaseManager.shared.startForPreview()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let featuredViewController = storyboard.instantiateViewController(identifier: "featuredViewController")
let navigationController = UINavigationController(rootViewController: featuredViewController)
navigationController.navigationBar.prefersLargeTitles = true
navigationController.modalPresentationStyle = .fullScreen
let viewController = UIViewController()
AppManager.shared.fetchSources() { (result) in
do
{
let (_, context) = try result.get()
try context.save()
}
catch let error as NSError
{
Logger.main.error("Failed to fetch sources for preview. \(error.localizedDescription, privacy: .public)")
}
}
AppManager.shared.updateKnownSources { result in
Task {
do
{
let knownSources = try result.get()
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
await withThrowingTaskGroup(of: Void.self) { taskGroup in
for source in knownSources.0
{
guard let sourceURL = source.sourceURL else { continue }
taskGroup.addTask {
_ = try await AppManager.shared.fetchSource(sourceURL: sourceURL, managedObjectContext: context)
}
}
}
await context.performAsync {
try! context.save()
}
await MainActor.run {
viewController.present(navigationController, animated: true)
}
}
catch
{
Logger.main.error("Failed to fetch known sources for preview. \(error.localizedDescription, privacy: .public)")
}
}
}
return viewController
}

View File

@@ -0,0 +1,52 @@
//
// AppBannerCollectionViewCell.swift
// AltStore
//
// Created by Riley Testut on 3/23/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import UIKit
class AppBannerCollectionViewCell: UICollectionViewListCell
{
let bannerView = AppBannerView(frame: .zero)
override init(frame: CGRect)
{
super.init(frame: frame)
self.initialize()
}
required init?(coder: NSCoder)
{
super.init(coder: coder)
self.initialize()
}
private func initialize()
{
// Prevent content "squishing" when scrolling offscreen.
self.insetsLayoutMarginsFromSafeArea = false
self.contentView.insetsLayoutMarginsFromSafeArea = false
self.bannerView.insetsLayoutMarginsFromSafeArea = false
self.backgroundView = UIView() // Clear background
self.selectedBackgroundView = UIView() // Disable selection highlighting.
self.contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.contentView.preservesSuperviewLayoutMargins = true
self.bannerView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(self.bannerView)
NSLayoutConstraint.activate([
self.bannerView.topAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.topAnchor),
self.bannerView.bottomAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.bottomAnchor),
self.bannerView.leadingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.leadingAnchor),
self.bannerView.trailingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.trailingAnchor),
])
}
}

View File

@@ -7,24 +7,341 @@
//
import UIKit
import AltStoreCore
import Roxas
import Nuke
extension AppBannerView
{
static let standardHeight = 88.0
enum Style
{
case app
case source
}
enum AppAction
{
case install
case open
case update
case custom(String)
}
}
class AppBannerView: RSTNibView
{
override var accessibilityLabel: String? {
get { return self.accessibilityView?.accessibilityLabel }
set { self.accessibilityView?.accessibilityLabel = newValue }
}
override open var accessibilityAttributedLabel: NSAttributedString? {
get { return self.accessibilityView?.accessibilityAttributedLabel }
set { self.accessibilityView?.accessibilityAttributedLabel = newValue }
}
override var accessibilityValue: String? {
get { return self.accessibilityView?.accessibilityValue }
set { self.accessibilityView?.accessibilityValue = newValue }
}
override open var accessibilityAttributedValue: NSAttributedString? {
get { return self.accessibilityView?.accessibilityAttributedValue }
set { self.accessibilityView?.accessibilityAttributedValue = newValue }
}
override open var accessibilityTraits: UIAccessibilityTraits {
get { return self.accessibilityView?.accessibilityTraits ?? [] }
set { self.accessibilityView?.accessibilityTraits = newValue }
}
var style: Style = .app
private var originalTintColor: UIColor?
@IBOutlet var titleLabel: UILabel!
@IBOutlet var subtitleLabel: UILabel!
@IBOutlet var iconImageView: AppIconImageView!
@IBOutlet var button: PillButton!
@IBOutlet var buttonLabel: UILabel!
@IBOutlet var betaBadgeView: UIView!
@IBOutlet var sourceIconImageView: AppIconImageView!
@IBOutlet var backgroundEffectView: UIVisualEffectView!
@IBOutlet private var vibrancyView: UIVisualEffectView!
@IBOutlet private var stackView: UIStackView!
@IBOutlet private var accessibilityView: UIView!
@IBOutlet private var iconImageViewHeightConstraint: NSLayoutConstraint!
override init(frame: CGRect)
{
super.init(frame: frame)
self.initialize()
}
required init?(coder: NSCoder)
{
super.init(coder: coder)
self.initialize()
}
private func initialize()
{
self.accessibilityView.accessibilityTraits.formUnion(.button)
self.isAccessibilityElement = false
self.accessibilityElements = [self.accessibilityView, self.button].compactMap { $0 }
self.betaBadgeView.isHidden = true
self.sourceIconImageView.style = .circular
self.sourceIconImageView.isHidden = true
self.layoutMargins = self.stackView.layoutMargins
self.insetsLayoutMarginsFromSafeArea = false
self.stackView.isLayoutMarginsRelativeArrangement = true
self.stackView.preservesSuperviewLayoutMargins = true
}
override func tintColorDidChange()
{
super.tintColorDidChange()
if self.tintAdjustmentMode != .dimmed
{
self.originalTintColor = self.tintColor
}
self.update()
}
}
extension AppBannerView
{
func configure(for app: AppProtocol, action: AppAction? = nil, showSourceIcon: Bool = true)
{
struct AppValues
{
var name: String
var developerName: String? = nil
var isBeta: Bool = false
init(app: AppProtocol)
{
self.name = app.name
guard let storeApp = (app as? StoreApp) ?? (app as? InstalledApp)?.storeApp else { return }
self.developerName = storeApp.developerName
if let track = storeApp.latestSupportedVersion?.channel,
ReleaseTracks.betaTracks.contains(track)
{
self.name = String(format: NSLocalizedString("%@ beta", comment: ""), app.name)
self.isBeta = true
}
}
}
self.style = .app
let values = AppValues(app: app)
self.titleLabel.text = app.name // Don't use values.name since that already includes "beta".
self.betaBadgeView.isHidden = !values.isBeta
if let developerName = values.developerName
{
self.subtitleLabel.text = developerName
self.accessibilityLabel = String(format: NSLocalizedString("%@ by %@", comment: ""), values.name, developerName)
}
else
{
self.subtitleLabel.text = NSLocalizedString("Sideloaded", comment: "")
self.accessibilityLabel = values.name
}
self.buttonLabel.isHidden = true
if let source = app.storeApp?.source, showSourceIcon
{
self.sourceIconImageView.isHidden = false
self.sourceIconImageView.backgroundColor = source.effectiveTintColor?.adjustedForDisplay ?? .altPrimary
if let iconURL = source.effectiveIconURL
{
if let image = ImageCache.shared[iconURL]
{
self.sourceIconImageView.backgroundColor = .white
self.sourceIconImageView.image = image.image
}
else
{
self.sourceIconImageView.image = nil
Nuke.loadImage(with: iconURL, into: self.sourceIconImageView) { result in
switch result
{
case .failure(let error): Logger.main.error("Failed to fetch source icon from \(iconURL, privacy: .public). \(error.localizedDescription, privacy: .public)")
case .success: self.sourceIconImageView.backgroundColor = .white // In case icon has transparent background.
}
}
}
}
}
else
{
self.sourceIconImageView.isHidden = true
}
let buttonAction: AppAction
if let action
{
buttonAction = action
}
else if let storeApp = app.storeApp
{
if let installedApp = storeApp.installedApp
{
// App is installed
// if installedApp.isUpdateAvailable
if installedApp.hasUpdate
{
buttonAction = .update
}
else
{
buttonAction = .open
}
}
else
{
// App is not installed
buttonAction = .install
}
}
else
{
// App is not from a source, fall back to .open
buttonAction = .open
}
UIView.performWithoutAnimation {
switch buttonAction
{
case .open:
let buttonTitle = NSLocalizedString("Open", comment: "")
self.button.setTitle(buttonTitle.uppercased(), for: .normal)
self.button.accessibilityLabel = String(format: NSLocalizedString("Open %@", comment: ""), values.name)
self.button.accessibilityValue = buttonTitle
self.button.countdownDate = nil
case .update:
let buttonTitle = NSLocalizedString("Update", comment: "")
self.button.setTitle(buttonTitle.uppercased(), for: .normal)
self.button.accessibilityLabel = String(format: NSLocalizedString("Update %@", comment: ""), values.name)
self.button.accessibilityValue = buttonTitle
self.button.countdownDate = nil
case .custom(let buttonTitle):
self.button.setTitle(buttonTitle, for: .normal)
self.button.accessibilityLabel = buttonTitle
self.button.accessibilityValue = buttonTitle
self.button.countdownDate = nil
case .install:
if let storeApp = app.storeApp, storeApp.isPledgeRequired
{
// Pledge required
if storeApp.isPledged
{
let buttonTitle = NSLocalizedString("Install", comment: "")
self.button.setTitle(buttonTitle.uppercased(), for: .normal)
self.button.accessibilityLabel = String(format: NSLocalizedString("Install %@", comment: ""), app.name)
self.button.accessibilityValue = buttonTitle
}
else
{
let buttonTitle = NSLocalizedString("Pledge", comment: "")
self.button.setTitle(buttonTitle.uppercased(), for: .normal)
self.button.accessibilityLabel = buttonTitle
self.button.accessibilityValue = buttonTitle
}
}
else
{
// Free app
let buttonTitle = NSLocalizedString("Free", comment: "")
self.button.setTitle(buttonTitle.uppercased(), for: .normal)
self.button.accessibilityLabel = String(format: NSLocalizedString("Download %@", comment: ""), app.name)
self.button.accessibilityValue = buttonTitle
}
if let versionDate = app.storeApp?.latestSupportedVersion?.date, versionDate > Date()
{
self.button.countdownDate = versionDate
}
else
{
self.button.countdownDate = nil
}
}
// Ensure PillButton is correct size before assigning progress.
self.layoutIfNeeded()
}
if let progress = AppManager.shared.installationProgress(for: app), progress.fractionCompleted < 1.0
{
self.button.progress = progress
}
else
{
self.button.progress = nil
}
}
func configure(for source: Source)
{
self.style = .source
let subtitle: String
if let text = source.subtitle
{
subtitle = text
}
else if let scheme = source.sourceURL.scheme
{
subtitle = source.sourceURL.absoluteString.replacingOccurrences(of: scheme + "://", with: "")
}
else
{
subtitle = source.sourceURL.absoluteString
}
self.titleLabel.text = source.name
self.subtitleLabel.text = subtitle
let tintColor = source.effectiveTintColor ?? .altPrimary
self.tintColor = tintColor
let accessibilityLabel = source.name + "\n" + subtitle
self.accessibilityLabel = accessibilityLabel
}
}
private extension AppBannerView
{
func update()
@@ -32,9 +349,48 @@ private extension AppBannerView
self.clipsToBounds = true
self.layer.cornerRadius = 22
self.subtitleLabel.textColor = self.tintColor
self.button.tintColor = self.tintColor
let tintColor = self.originalTintColor ?? self.tintColor
self.subtitleLabel.textColor = tintColor
self.backgroundColor = self.tintColor.withAlphaComponent(0.1)
switch self.style
{
case .app:
self.directionalLayoutMargins.trailing = self.stackView.directionalLayoutMargins.trailing
self.iconImageViewHeightConstraint.constant = 60
self.iconImageView.style = .icon
self.titleLabel.textColor = .label
self.button.style = .pill
self.backgroundEffectView.contentView.backgroundColor = UIColor(resource: .blurTint)
self.backgroundEffectView.backgroundColor = tintColor
case .source:
self.directionalLayoutMargins.trailing = 20
self.iconImageViewHeightConstraint.constant = 44
self.iconImageView.style = .circular
self.titleLabel.textColor = .white
self.button.style = .custom
self.backgroundEffectView.contentView.backgroundColor = tintColor?.adjustedForDisplay
self.backgroundEffectView.backgroundColor = nil
if let tintColor, tintColor.isTooBright
{
let textVibrancyEffect = UIVibrancyEffect(blurEffect: .init(style: .systemChromeMaterialLight), style: .fill)
self.vibrancyView.effect = textVibrancyEffect
}
else
{
// Thinner == more dull
let textVibrancyEffect = UIVibrancyEffect(blurEffect: .init(style: .systemThinMaterialDark), style: .secondaryLabel)
self.vibrancyView.effect = textVibrancyEffect
}
}
}
}

View File

@@ -1,21 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22155" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="AppBannerView" customModule="AltStore" customModuleProvider="target">
<connections>
<outlet property="accessibilityView" destination="bJL-Yw-i4u" id="PWe-tw-jDA"/>
<outlet property="backgroundEffectView" destination="rZk-be-tiI" id="fzU-VT-JeW"/>
<outlet property="betaBadgeView" destination="qQl-Ez-zC5" id="6O1-Cx-7qz"/>
<outlet property="button" destination="tVx-3G-dcu" id="joa-AH-syX"/>
<outlet property="buttonLabel" destination="Yd9-jw-faD" id="o7g-Gb-CIt"/>
<outlet property="iconImageView" destination="avS-dx-4iy" id="TQs-Ej-gin"/>
<outlet property="iconImageViewHeightConstraint" destination="6lU-H8-nEw" id="PSt-Xa-lQT"/>
<outlet property="sourceIconImageView" destination="dku-SJ-aay" id="rA0-y1-dIb"/>
<outlet property="stackView" destination="d1T-UD-gWG" id="E7N-Zb-lm1"/>
<outlet property="subtitleLabel" destination="oN5-vu-Dnw" id="gA4-iJ-Tix"/>
<outlet property="titleLabel" destination="mFe-zJ-eva" id="2OH-f8-cid"/>
<outlet property="vibrancyView" destination="fC4-1V-iMn" id="PXE-2B-A7w"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
@@ -23,47 +30,93 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="88"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" alignment="center" spacing="11" translatesAutoresizingMaskIntoConstraints="NO" id="d1T-UD-gWG" userLabel="App Info">
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bJL-Yw-i4u">
<rect key="frame" x="0.0" y="0.0" width="375" height="88"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<accessibility key="accessibilityConfiguration">
<bool key="isElement" value="YES"/>
</accessibility>
</view>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rZk-be-tiI">
<rect key="frame" x="0.0" y="0.0" width="375" height="88"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="b8k-up-HtI">
<rect key="frame" x="0.0" y="0.0" width="375" height="88"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" name="BlurTint"/>
</view>
<blurEffect style="systemChromeMaterial"/>
</visualEffectView>
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" alignment="center" spacing="11" translatesAutoresizingMaskIntoConstraints="NO" id="d1T-UD-gWG" userLabel="App Info">
<rect key="frame" x="0.0" y="0.0" width="375" height="88"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="avS-dx-4iy" customClass="AppIconImageView" customModule="AltStore" customModuleProvider="target">
<rect key="frame" x="14" y="14" width="60" height="60"/>
<rect key="frame" x="16" y="14" width="60" height="60"/>
<constraints>
<constraint firstAttribute="height" constant="60" id="6lU-H8-nEw"/>
<constraint firstAttribute="width" secondItem="avS-dx-4iy" secondAttribute="height" multiplier="1:1" id="AYT-3g-wcV"/>
</constraints>
</imageView>
<stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="100" insetsLayoutMarginsFromSafeArea="NO" axis="vertical" alignment="top" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="caL-vN-Svn">
<rect key="frame" x="85" y="24" width="195" height="40.5"/>
<rect key="frame" x="87" y="25.5" width="184" height="37.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="1Es-pv-zwd">
<rect key="frame" x="0.0" y="0.0" width="135" height="21.5"/>
<stackView opaque="NO" contentMode="scaleToFill" horizontalCompressionResistancePriority="500" alignment="center" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="1Es-pv-zwd">
<rect key="frame" x="0.0" y="0.0" width="147" height="19.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" horizontalCompressionResistancePriority="500" text="App Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="mFe-zJ-eva">
<rect key="frame" x="0.0" y="0.0" width="88" height="21.5"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="18"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="400" verticalHuggingPriority="251" horizontalCompressionResistancePriority="500" text="App Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="mFe-zJ-eva">
<rect key="frame" x="0.0" y="0.0" width="79" height="19.5"/>
<accessibility key="accessibilityConfiguration" identifier="NameLabel"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="16"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="dku-SJ-aay" customClass="AppIconImageView" customModule="AltStore" customModuleProvider="target">
<rect key="frame" x="84" y="1" width="17" height="17"/>
<constraints>
<constraint firstAttribute="width" secondItem="dku-SJ-aay" secondAttribute="height" id="VKw-lc-8NQ"/>
<constraint firstAttribute="width" constant="17" id="hAe-gc-Ehh"/>
</constraints>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="BetaBadge" translatesAutoresizingMaskIntoConstraints="NO" id="qQl-Ez-zC5">
<rect key="frame" x="94" y="0.0" width="41" height="21.5"/>
<rect key="frame" x="106" y="1" width="41" height="17"/>
<accessibility key="accessibilityConfiguration" identifier="Beta Badge">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
<bool key="isElement" value="YES"/>
</accessibility>
</imageView>
</subviews>
</stackView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Developer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="oN5-vu-Dnw">
<rect key="frame" x="0.0" y="23.5" width="66" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fC4-1V-iMn">
<rect key="frame" x="0.0" y="21.5" width="62" height="16"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="LQh-pN-ePC">
<rect key="frame" x="0.0" y="0.0" width="62" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="750" text="Developer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="oN5-vu-Dnw">
<rect key="frame" x="0.0" y="0.0" width="62" height="16"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="oN5-vu-Dnw" firstAttribute="top" secondItem="LQh-pN-ePC" secondAttribute="top" id="7RH-WP-LzL"/>
<constraint firstItem="oN5-vu-Dnw" firstAttribute="leading" secondItem="LQh-pN-ePC" secondAttribute="leading" id="By8-cR-kTu"/>
<constraint firstAttribute="trailing" secondItem="oN5-vu-Dnw" secondAttribute="trailing" id="Hiv-6y-XrH"/>
<constraint firstAttribute="bottom" secondItem="oN5-vu-Dnw" secondAttribute="bottom" id="yc2-Dr-Qnv"/>
</constraints>
</view>
<vibrancyEffect style="secondaryLabel">
<blurEffect style="systemChromeMaterial"/>
</vibrancyEffect>
</visualEffectView>
</subviews>
</stackView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tVx-3G-dcu" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
<rect key="frame" x="291" y="28.5" width="72" height="31"/>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="900" horizontalCompressionResistancePriority="900" placeholderIntrinsicWidth="77" placeholderIntrinsicHeight="31" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tVx-3G-dcu" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
<rect key="frame" x="282" y="28.5" width="77" height="31"/>
<color key="backgroundColor" red="0.22352941179999999" green="0.4941176471" blue="0.39607843139999999" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" secondItem="tVx-3G-dcu" secondAttribute="height" priority="999" id="Vbk-VH-5eU"/>
<constraint firstAttribute="height" constant="31" id="Zwh-yQ-GTu"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="72" id="eGc-Dk-QbL"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
<state key="normal" title="FREE"/>
@@ -71,17 +124,40 @@
</subviews>
<edgeInsets key="layoutMargins" top="14" left="14" bottom="14" right="12"/>
</stackView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Yd9-jw-faD">
<rect key="frame" x="307" y="12.5" width="27" height="12"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="10"/>
<color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="d1T-UD-gWG" secondAttribute="bottom" id="B9e-Mf-cy5"/>
<constraint firstAttribute="trailing" secondItem="d1T-UD-gWG" secondAttribute="trailing" id="HcT-2k-z0H"/>
<constraint firstItem="d1T-UD-gWG" firstAttribute="leading" secondItem="FxI-Fh-ll5" secondAttribute="leading" id="PIM-W5-dkh"/>
<constraint firstItem="d1T-UD-gWG" firstAttribute="top" secondItem="FxI-Fh-ll5" secondAttribute="top" id="RHn-ZK-jgl"/>
<constraint firstItem="d1T-UD-gWG" firstAttribute="leading" secondItem="FxI-Fh-ll5" secondAttribute="leading" id="5tv-QN-ZWU"/>
<constraint firstAttribute="bottom" secondItem="bJL-Yw-i4u" secondAttribute="bottom" id="FRq-ZD-2rE"/>
<constraint firstItem="rZk-be-tiI" firstAttribute="top" secondItem="FxI-Fh-ll5" secondAttribute="top" id="N6R-B2-Rie"/>
<constraint firstItem="Yd9-jw-faD" firstAttribute="centerX" secondItem="tVx-3G-dcu" secondAttribute="centerX" id="acx-pf-8hH"/>
<constraint firstItem="bJL-Yw-i4u" firstAttribute="top" secondItem="FxI-Fh-ll5" secondAttribute="top" id="h6T-q1-YV9"/>
<constraint firstItem="tVx-3G-dcu" firstAttribute="top" secondItem="Yd9-jw-faD" secondAttribute="bottom" constant="4" id="hTD-wh-KV8"/>
<constraint firstAttribute="trailing" secondItem="d1T-UD-gWG" secondAttribute="trailing" id="mlG-w3-Ly6"/>
<constraint firstAttribute="trailing" secondItem="rZk-be-tiI" secondAttribute="trailing" id="nEy-pm-Fcs"/>
<constraint firstAttribute="bottom" secondItem="d1T-UD-gWG" secondAttribute="bottom" priority="999" id="nJo-To-LmX"/>
<constraint firstItem="bJL-Yw-i4u" firstAttribute="leading" secondItem="FxI-Fh-ll5" secondAttribute="leading" id="oLt-2z-QoJ"/>
<constraint firstItem="rZk-be-tiI" firstAttribute="leading" secondItem="FxI-Fh-ll5" secondAttribute="leading" id="pGD-Tl-U4c"/>
<constraint firstItem="d1T-UD-gWG" firstAttribute="top" secondItem="FxI-Fh-ll5" secondAttribute="top" id="q2p-0S-Nv5"/>
<constraint firstAttribute="trailing" secondItem="bJL-Yw-i4u" secondAttribute="trailing" id="vwx-P9-dlB"/>
<constraint firstAttribute="bottom" secondItem="rZk-be-tiI" secondAttribute="bottom" id="yk0-pw-joP"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="139.85507246376812" y="152.67857142857142"/>
</view>
</objects>
<resources>
<image name="BetaBadge" width="41" height="17"/>
<namedColor name="BlurTint">
<color red="1" green="1" blue="1" alpha="0.30000001192092896" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<systemColor name="secondaryLabelColor">
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>

View File

@@ -0,0 +1,387 @@
//
// AppCardCollectionViewCell.swift
// AltStore
//
// Created by Riley Testut on 10/13/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import UIKit
import AltStoreCore
import Roxas
import Nuke
private let minimumItemSpacing = 8.0
class AppCardCollectionViewCell: UICollectionViewCell
{
let bannerView: AppBannerView
let captionLabel: UILabel
var prefersPagingScreenshots = true
private let screenshotsCollectionView: UICollectionView
private let stackView: UIStackView
private let topAreaPanGestureRecognizer: UIPanGestureRecognizer
private lazy var dataSource = self.makeDataSource()
private var screenshots: [AppScreenshot] = [] {
didSet {
self.dataSource.items = self.screenshots
if self.screenshots.isEmpty
{
// No screenshots, so hide collection view.
self.collectionViewAspectRatioConstraint.isActive = false
self.stackView.layoutMargins.bottom = 0
}
else
{
// At least one screenshot, so show collection view.
self.collectionViewAspectRatioConstraint.isActive = true
self.stackView.layoutMargins.bottom = self.screenshotsCollectionView.directionalLayoutMargins.leading
}
}
}
private let collectionViewAspectRatioConstraint: NSLayoutConstraint
override init(frame: CGRect)
{
self.bannerView = AppBannerView(frame: .zero)
self.bannerView.layoutMargins.bottom = 0
let vibrancyEffect = UIVibrancyEffect(blurEffect: UIBlurEffect(style: .systemChromeMaterial), style: .secondaryLabel)
let captionVibrancyView = UIVisualEffectView(effect: vibrancyEffect)
self.captionLabel = UILabel(frame: .zero)
self.captionLabel.font = UIFont(descriptor: UIFontDescriptor.preferredFontDescriptor(withTextStyle: .footnote).bolded(), size: 0)
self.captionLabel.textAlignment = .center
self.captionLabel.numberOfLines = 2
self.captionLabel.minimumScaleFactor = 0.8
captionVibrancyView.contentView.addSubview(self.captionLabel, pinningEdgesWith: .zero)
self.screenshotsCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
self.screenshotsCollectionView.backgroundColor = nil
self.screenshotsCollectionView.alwaysBounceVertical = false
self.screenshotsCollectionView.alwaysBounceHorizontal = true
self.screenshotsCollectionView.showsHorizontalScrollIndicator = false
self.screenshotsCollectionView.showsVerticalScrollIndicator = false
self.stackView = UIStackView(arrangedSubviews: [self.bannerView, captionVibrancyView, self.screenshotsCollectionView])
self.stackView.translatesAutoresizingMaskIntoConstraints = false
self.stackView.spacing = 12
self.stackView.axis = .vertical
self.stackView.alignment = .fill
self.stackView.distribution = .equalSpacing
// Aspect ratio constraint to fit exactly 3 modern portrait iPhone screenshots side-by-side (with spacing).
let inset = self.bannerView.layoutMargins.left
let multiplier = (AppScreenshot.defaultAspectRatio.width * 3) / AppScreenshot.defaultAspectRatio.height
let spacing = (inset * 2) + (minimumItemSpacing * 2)
self.collectionViewAspectRatioConstraint = self.screenshotsCollectionView.widthAnchor.constraint(equalTo: self.screenshotsCollectionView.heightAnchor, multiplier: multiplier, constant: spacing)
// Allows us to ignore swipes in top portion of screenshotsCollectionView.
self.topAreaPanGestureRecognizer = UIPanGestureRecognizer(target: nil, action: nil)
self.topAreaPanGestureRecognizer.cancelsTouchesInView = false
self.topAreaPanGestureRecognizer.delaysTouchesBegan = false
self.topAreaPanGestureRecognizer.delaysTouchesEnded = false
super.init(frame: frame)
self.contentView.clipsToBounds = true
self.contentView.layer.cornerCurve = .continuous
self.contentView.addSubview(self.bannerView.backgroundEffectView, pinningEdgesWith: .zero)
self.contentView.addSubview(self.stackView, pinningEdgesWith: .zero)
self.screenshotsCollectionView.collectionViewLayout = self.makeLayout()
self.screenshotsCollectionView.dataSource = self.dataSource
self.screenshotsCollectionView.prefetchDataSource = self.dataSource
// Adding screenshotsCollectionView's gesture recognizers to self.contentView breaks paging,
// so instead we intercept taps and pass them onto delegate.
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(AppCardCollectionViewCell.handleTapGesture(_:)))
tapGestureRecognizer.cancelsTouchesInView = false
tapGestureRecognizer.delaysTouchesBegan = false
tapGestureRecognizer.delaysTouchesEnded = false
self.screenshotsCollectionView.addGestureRecognizer(tapGestureRecognizer)
self.topAreaPanGestureRecognizer.delegate = self
self.screenshotsCollectionView.panGestureRecognizer.require(toFail: self.topAreaPanGestureRecognizer)
self.screenshotsCollectionView.addGestureRecognizer(self.topAreaPanGestureRecognizer)
self.screenshotsCollectionView.register(AppScreenshotCollectionViewCell.self, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier)
self.stackView.isLayoutMarginsRelativeArrangement = true
self.stackView.layoutMargins.bottom = inset
self.contentView.preservesSuperviewLayoutMargins = true
self.screenshotsCollectionView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: inset, bottom: 0, trailing: inset)
NSLayoutConstraint.activate([
self.bannerView.heightAnchor.constraint(equalToConstant: AppBannerView.standardHeight - inset)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews()
{
super.layoutSubviews()
self.contentView.layer.cornerRadius = self.bannerView.layer.cornerRadius
}
}
private extension AppCardCollectionViewCell
{
func makeLayout() -> UICollectionViewCompositionalLayout
{
let layoutConfig = UICollectionViewCompositionalLayoutConfiguration()
layoutConfig.contentInsetsReference = .layoutMargins
let layout = UICollectionViewCompositionalLayout(sectionProvider: { [weak self] (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
guard let self else { return nil }
var contentWidth = 0.0
var numberOfVisibleScreenshots = 0
for screenshot in self.screenshots
{
var aspectRatio = screenshot.aspectRatio
if aspectRatio.width > aspectRatio.height
{
switch screenshot.deviceType
{
case .iphone:
// Always rotate landscape iPhone screenshots
aspectRatio = CGSize(width: aspectRatio.height, height: aspectRatio.width)
case .ipad:
// Never rotate iPad screenshots
break
default: break
}
}
let screenshotWidth = (layoutEnvironment.container.effectiveContentSize.height * (aspectRatio.width / aspectRatio.height)).rounded(.up) // Round to ensure we over-estimate contentWidth.
let totalContentWidth = contentWidth + (screenshotWidth + minimumItemSpacing)
if totalContentWidth > layoutEnvironment.container.effectiveContentSize.width
{
// totalContentWidth is larger than visible width.
break
}
contentWidth = totalContentWidth
numberOfVisibleScreenshots += 1
}
// Use .estimated(1) to ensure we don't over-estimate widths, which can cause incorrect layouts for the last group.
let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(1), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
if numberOfVisibleScreenshots == 1
{
// If there's only one screenshot visible initially, we'll (reluctantly) opt-in to flexible spacing on both sides.
// This ensures the items are always centered, but may result in larger spacings between items than we'd prefer.
item.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .flexible(0), top: nil, trailing: .flexible(0), bottom: nil)
}
else
{
// Otherwise, only have flexible spacing on the leading edge, which will be balanced by trailingGroup's flexible trailing spacing.
item.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .flexible(0), top: nil, trailing: nil, bottom: nil)
}
let groupItem = NSCollectionLayoutItem(layoutSize: itemSize)
let trailingGroup = NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitems: [groupItem])
trailingGroup.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: nil, top: nil, trailing: .flexible(0), bottom: nil)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item, trailingGroup])
group.interItemSpacing = .fixed(minimumItemSpacing)
if numberOfVisibleScreenshots < self.screenshots.count
{
// There are more screenshots than what is displayed, so no need to manually center them.
}
else
{
// We're showing all screenshots initially, so make sure they're centered.
let insetWidth = (layoutEnvironment.container.effectiveContentSize.width - contentWidth) / 2.0
group.contentInsets.leading = (insetWidth - 1).rounded(.down) // Subtract 1 to avoid overflowing/clipping
}
let layoutSection = NSCollectionLayoutSection(group: group)
layoutSection.orthogonalScrollingBehavior = .groupPagingCentered
layoutSection.interGroupSpacing = self.screenshotsCollectionView.directionalLayoutMargins.leading + self.screenshotsCollectionView.directionalLayoutMargins.trailing
return layoutSection
}, configuration: layoutConfig)
return layout
}
func makeDataSource() -> RSTArrayCollectionViewPrefetchingDataSource<AppScreenshot, UIImage>
{
let dataSource = RSTArrayCollectionViewPrefetchingDataSource<AppScreenshot, UIImage>(items: [])
dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in
let cell = cell as! AppScreenshotCollectionViewCell
cell.imageView.image = nil
cell.imageView.isIndicatingActivity = true
var aspectRatio = screenshot.aspectRatio
if aspectRatio.width > aspectRatio.height
{
switch screenshot.deviceType
{
case .iphone:
// Always rotate landscape iPhone screenshots
aspectRatio = CGSize(width: aspectRatio.height, height: aspectRatio.width)
case .ipad:
// Never rotate iPad screenshots
break
default: break
}
}
cell.aspectRatio = aspectRatio
}
dataSource.prefetchHandler = { (screenshot, indexPath, completionHandler) in
let imageURL = screenshot.imageURL
return RSTAsyncBlockOperation() { (operation) in
let request = ImageRequest(url: imageURL)
ImagePipeline.shared.loadImage(with: request, progress: nil) { result in
guard !operation.isCancelled else { return operation.finish() }
switch result
{
case .success(let response): completionHandler(response.image, nil)
case .failure(let error): completionHandler(nil, error)
}
}
}
}
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
let cell = cell as! AppScreenshotCollectionViewCell
cell.imageView.isIndicatingActivity = false
cell.setImage(image)
if let error = error
{
print("Error loading image:", error)
}
}
return dataSource
}
@objc func handleTapGesture(_ tapGesture: UITapGestureRecognizer)
{
var superview: UIView? = self.superview
var collectionView: UICollectionView? = nil
while case let view? = superview
{
if let cv = view as? UICollectionView
{
collectionView = cv
break
}
superview = view.superview
}
if let collectionView, let indexPath = collectionView.indexPath(for: self)
{
collectionView.delegate?.collectionView?(collectionView, didSelectItemAt: indexPath)
}
}
}
extension AppCardCollectionViewCell
{
func configure(for storeApp: StoreApp, showSourceIcon: Bool = true)
{
self.screenshots = storeApp.preferredScreenshots()
// Explicitly set to false to ensure we're starting from a non-activity indicating state.
// Otherwise, cell reuse can mess up some cached values.
self.bannerView.button.isIndicatingActivity = false
self.bannerView.tintColor = storeApp.tintColor
self.bannerView.configure(for: storeApp, showSourceIcon: showSourceIcon)
self.bannerView.subtitleLabel.numberOfLines = 1
self.bannerView.subtitleLabel.lineBreakMode = .byTruncatingTail
self.bannerView.subtitleLabel.minimumScaleFactor = 0.8
self.bannerView.subtitleLabel.text = storeApp.developerName
if let subtitle = storeApp.subtitle, !subtitle.isEmpty
{
self.captionLabel.text = subtitle
self.captionLabel.isHidden = false
}
else
{
self.captionLabel.isHidden = true
}
}
}
extension AppCardCollectionViewCell: UIGestureRecognizerDelegate
{
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool
{
// Never recognize topAreaPanGestureRecognizer unless prefersPagingScreenshots is false.
guard !self.prefersPagingScreenshots else { return false }
let point = gestureRecognizer.location(in: self.screenshotsCollectionView)
// Top area = Top 3/4
let isTopArea = point.y < (self.screenshotsCollectionView.bounds.height / 4) * 3
return isTopArea
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool
{
guard let panGestureRecognizer = otherGestureRecognizer as? UIPanGestureRecognizer, let view = panGestureRecognizer.view else { return false }
if view.isDescendant(of: self.screenshotsCollectionView)
{
// Only allow nested gesture recognizers if topAreaPanGestureRecognizer fails.
return true
}
else
{
// Always allow parent gesture recognizers.
return false
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
{
guard let panGestureRecognizer = otherGestureRecognizer as? UIPanGestureRecognizer, let view = panGestureRecognizer.view else { return true }
if view.isDescendant(of: self.screenshotsCollectionView)
{
// Don't recognize topAreaPanGestureRecognizer alongside nested gesture recognizers.
return false
}
else
{
// Allow recognizing simultaneously with parent gesture recognizers.
// This fixes accidentally breaking scrolling in parent.
return true
}
}
}

View File

@@ -8,40 +8,62 @@
import UIKit
extension AppIconImageView
{
enum Style
{
case icon
case circular
}
}
class AppIconImageView: UIImageView
{
override func awakeFromNib()
var style: Style = .icon {
didSet {
self.setNeedsLayout()
}
}
init(style: Style)
{
super.awakeFromNib()
self.style = style
super.init(image: nil)
self.initialize()
}
required init?(coder: NSCoder)
{
super.init(coder: coder)
self.initialize()
}
private func initialize()
{
self.contentMode = .scaleAspectFill
self.clipsToBounds = true
self.backgroundColor = .white
self.layer.borderWidth = 0.5
self.layer.borderColor = self.tintColor.cgColor
// Allows us to match system look for app icons.
if self.layer.responds(to: Selector(("continuousCorners")))
{
self.layer.setValue(true, forKey: "continuousCorners")
}
self.layer.cornerCurve = .continuous
}
override func layoutSubviews()
{
super.layoutSubviews()
// Based off of 60pt icon having 12pt radius.
let radius = self.bounds.height / 5
self.layer.cornerRadius = radius
}
override func tintColorDidChange()
{
super.tintColorDidChange()
self.layer.borderColor = self.tintColor.cgColor
switch self.style
{
case .icon:
// Based off of 60pt icon having 12pt radius.
let radius = self.bounds.height / 5
self.layer.cornerRadius = radius
case .circular:
let radius = self.bounds.height / 2
self.layer.cornerRadius = radius
}
}
}

View File

@@ -8,7 +8,7 @@
import AVFoundation
class BackgroundTaskManager
final class BackgroundTaskManager
{
static let shared = BackgroundTaskManager()

View File

@@ -8,7 +8,7 @@
import UIKit
class Button: UIButton
final class Button: UIButton
{
override var intrinsicContentSize: CGSize {
var size = super.intrinsicContentSize

View File

@@ -8,10 +8,11 @@
import UIKit
class CollapsingTextView: UITextView
final class CollapsingTextView: UITextView
{
var isCollapsed = true {
didSet {
guard self.isCollapsed != oldValue else { return }
self.setNeedsLayout()
}
}
@@ -22,19 +23,59 @@ class CollapsingTextView: UITextView
}
}
var lineSpacing: CGFloat = 2 {
var lineSpacing: Double = 2 {
didSet {
self.setNeedsLayout()
if #available(iOS 16, *)
{
self.updateText()
}
else
{
self.setNeedsLayout()
}
}
}
override var text: String! {
didSet {
guard #available(iOS 16, *) else { return }
self.updateText()
}
}
let moreButton = UIButton(type: .system)
override init(frame: CGRect, textContainer: NSTextContainer?)
{
super.init(frame: frame, textContainer: textContainer)
self.initialize()
}
required init?(coder: NSCoder)
{
super.init(coder: coder)
}
override func awakeFromNib()
{
super.awakeFromNib()
self.layoutManager.delegate = self
self.initialize()
}
private func initialize()
{
if #available(iOS 16, *)
{
self.updateText()
}
else
{
self.layoutManager.delegate = self
}
self.textContainerInset = .zero
self.textContainer.lineFragmentPadding = 0
@@ -69,11 +110,13 @@ class CollapsingTextView: UITextView
if self.isCollapsed
{
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
let boundingSize = self.attributedText.boundingRect(with: CGSize(width: self.textContainer.size.width, height: .infinity), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
let maximumCollapsedHeight = font.lineHeight * Double(self.maximumNumberOfLines) + self.lineSpacing * Double(self.maximumNumberOfLines - 1)
let maximumCollapsedHeight = font.lineHeight * CGFloat(self.maximumNumberOfLines)
if self.intrinsicContentSize.height > maximumCollapsedHeight
if boundingSize.height.rounded() > maximumCollapsedHeight.rounded()
{
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
var exclusionFrame = moreButtonFrame
exclusionFrame.origin.y += self.moreButton.bounds.midY
exclusionFrame.size.width = self.bounds.width // Extra wide to make sure it wraps to next line.
@@ -83,6 +126,7 @@ class CollapsingTextView: UITextView
}
else
{
self.textContainer.maximumNumberOfLines = 0 // Fixes last line having slightly smaller line spacing.
self.textContainer.exclusionPaths = []
self.moreButton.isHidden = true
@@ -106,6 +150,25 @@ private extension CollapsingTextView
{
self.isCollapsed.toggle()
}
@available(iOS 16, *)
func updateText()
{
do
{
let style = NSMutableParagraphStyle()
style.lineSpacing = self.lineSpacing
var attributedText = try AttributedString(self.attributedText, including: \.uiKit)
attributedText[AttributeScopes.UIKitAttributes.ParagraphStyleAttribute.self] = style
self.attributedText = NSAttributedString(attributedText)
}
catch
{
print("[ALTLog] Failed to update CollapsingTextView line spacing:", error)
}
}
}
extension CollapsingTextView: NSLayoutManagerDelegate

View File

@@ -0,0 +1,20 @@
//
// ForwardingNavigationController.swift
// AltStore
//
// Created by Riley Testut on 10/24/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
final class ForwardingNavigationController: UINavigationController
{
override var childForStatusBarStyle: UIViewController? {
return self.topViewController
}
override var childForStatusBarHidden: UIViewController? {
return self.topViewController
}
}

View File

@@ -0,0 +1,652 @@
//
// HeaderContentViewController.swift
// AltStore
//
// Created by Riley Testut on 3/10/23.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
import AltStoreCore
import Roxas
import Nuke
protocol ScrollableContentViewController: UIViewController
{
var scrollView: UIScrollView { get }
}
class HeaderContentViewController<Header: UIView, Content: ScrollableContentViewController> : UIViewController,
UIAdaptivePresentationControllerDelegate,
UIScrollViewDelegate,
UIGestureRecognizerDelegate
{
var tintColor: UIColor? {
didSet {
guard self.isViewLoaded else { return }
self.view.tintColor = self.tintColor?.adjustedForDisplay
self.update()
}
}
private(set) var headerView: Header!
private(set) var contentViewController: Content!
private(set) var backButton: VibrantButton!
private(set) var backgroundImageView: UIImageView!
private(set) var navigationBarNameLabel: UILabel!
private(set) var navigationBarIconView: UIImageView!
private(set) var navigationBarTitleView: UIStackView!
private(set) var navigationBarButton: PillButton!
private var scrollView: UIScrollView!
private var headerScrollView: UIScrollView!
private var headerContainerView: UIView!
private var backgroundBlurView: UIVisualEffectView!
private var contentViewControllerShadowView: UIView!
private var ignoreBackGestureRecognizer: UIPanGestureRecognizer!
private var blurAnimator: UIViewPropertyAnimator?
private var navigationBarAnimator: UIViewPropertyAnimator?
private var contentSizeObservation: NSKeyValueObservation?
private var _shouldResetLayout = false
private var _backgroundBlurEffect: UIBlurEffect?
private var _backgroundBlurTintColor: UIColor?
private var isViewingHeader: Bool {
let isViewingHeader = (self.headerScrollView.contentOffset.x != self.headerScrollView.contentInset.left)
return isViewingHeader
}
override var preferredStatusBarStyle: UIStatusBarStyle {
if #available(iOS 17, *)
{
// On iOS 17+, .default will update the status bar automatically.
return .default
}
else
{
return _preferredStatusBarStyle
}
}
private var _preferredStatusBarStyle: UIStatusBarStyle = .default
init()
{
super.init(nibName: nil, bundle: nil)
}
deinit
{
self.blurAnimator?.stopAnimation(true)
self.navigationBarAnimator?.stopAnimation(true)
}
required init?(coder: NSCoder)
{
super.init(coder: coder)
}
func makeContentViewController() -> Content
{
fatalError()
}
func makeHeaderView() -> Header
{
fatalError()
}
override func viewDidLoad()
{
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.clipsToBounds = true
self.navigationItem.largeTitleDisplayMode = .never
self.navigationController?.presentationController?.delegate = self
// Background
self.backgroundImageView = UIImageView(frame: .zero)
self.backgroundImageView.contentMode = .scaleAspectFill
self.view.addSubview(self.backgroundImageView)
let blurEffect = UIBlurEffect(style: .regular)
self.backgroundBlurView = UIVisualEffectView(effect: blurEffect)
self.view.addSubview(self.backgroundBlurView, pinningEdgesWith: .zero)
// Header View
self.headerContainerView = UIView(frame: .zero)
self.view.addSubview(self.headerContainerView, pinningEdgesWith: .zero)
self.ignoreBackGestureRecognizer = UIPanGestureRecognizer(target: self, action: nil)
self.ignoreBackGestureRecognizer.delegate = self
self.headerContainerView.addGestureRecognizer(self.ignoreBackGestureRecognizer)
self.navigationController?.interactivePopGestureRecognizer?.require(toFail: self.ignoreBackGestureRecognizer) // So we can disable back gesture when viewing header.
self.headerScrollView = UIScrollView(frame: .zero)
self.headerScrollView.delegate = self
self.headerScrollView.isPagingEnabled = true
self.headerScrollView.clipsToBounds = false
self.headerScrollView.indicatorStyle = .white
self.headerScrollView.showsVerticalScrollIndicator = false
self.headerContainerView.addSubview(self.headerScrollView)
self.headerContainerView.addGestureRecognizer(self.headerScrollView.panGestureRecognizer) // Allow panning outside headerScrollView bounds.
self.headerView = self.makeHeaderView()
self.headerScrollView.addSubview(self.headerView)
let imageConfiguration = UIImage.SymbolConfiguration(weight: .semibold)
let image = UIImage(systemName: "chevron.backward", withConfiguration: imageConfiguration)
self.backButton = VibrantButton(type: .system)
self.backButton.image = image
self.backButton.tintColor = self.tintColor
self.backButton.sizeToFit()
self.backButton.addTarget(self.navigationController, action: #selector(UINavigationController.popViewController(animated:)), for: .primaryActionTriggered)
self.view.addSubview(self.backButton)
// Content View Controller
self.contentViewController = self.makeContentViewController()
self.contentViewController.view.frame = self.view.bounds
self.contentViewController.view.layer.cornerRadius = 38
self.contentViewController.view.layer.masksToBounds = true
self.addChild(self.contentViewController)
self.view.addSubview(self.contentViewController.view)
self.contentViewController.didMove(toParent: self)
self.contentViewControllerShadowView = UIView()
self.contentViewControllerShadowView.backgroundColor = .white
self.contentViewControllerShadowView.layer.cornerRadius = 38
self.contentViewControllerShadowView.layer.shadowColor = UIColor.black.cgColor
self.contentViewControllerShadowView.layer.shadowOffset = CGSize(width: 0, height: -1)
self.contentViewControllerShadowView.layer.shadowRadius = 10
self.contentViewControllerShadowView.layer.shadowOpacity = 0.3
self.view.insertSubview(self.contentViewControllerShadowView, belowSubview: self.contentViewController.view)
// Add scrollView to front so the scroll indicators are visible, but disable user interaction.
self.scrollView = UIScrollView(frame: CGRect(origin: .zero, size: self.view.bounds.size))
self.scrollView.delegate = self
self.scrollView.isUserInteractionEnabled = false
self.scrollView.contentInsetAdjustmentBehavior = .never
self.view.addSubview(self.scrollView, pinningEdgesWith: .zero)
self.view.addGestureRecognizer(self.scrollView.panGestureRecognizer)
self.contentViewController.scrollView.panGestureRecognizer.require(toFail: self.scrollView.panGestureRecognizer)
self.contentViewController.scrollView.showsVerticalScrollIndicator = false
self.contentViewController.scrollView.contentInsetAdjustmentBehavior = .never
// Navigation Bar Title View
self.navigationBarNameLabel = UILabel(frame: .zero)
self.navigationBarNameLabel.font = UIFont.boldSystemFont(ofSize: 17) // We want semibold, which this (apparently) returns.
self.navigationBarNameLabel.text = self.title
self.navigationBarNameLabel.sizeToFit()
self.navigationBarIconView = UIImageView(frame: .zero)
self.navigationBarIconView.clipsToBounds = true
self.navigationBarTitleView = UIStackView(arrangedSubviews: [self.navigationBarIconView, self.navigationBarNameLabel])
self.navigationBarTitleView.axis = .horizontal
self.navigationBarTitleView.spacing = 8
self.navigationBarButton = PillButton(type: .system)
self.navigationBarButton.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 9000), for: .horizontal) // Prioritize over title length.
if #available(iOS 26.0, *) {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: self.navigationBarButton)
} else {
// Embed navigationBarButton in container view with Auto Layout to ensure it can automatically update its size.
let buttonContainerView = UIView()
buttonContainerView.addSubview(self.navigationBarButton, pinningEdgesWith: .zero)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: buttonContainerView)
}
NSLayoutConstraint.activate([
self.navigationBarIconView.widthAnchor.constraint(equalToConstant: 35),
self.navigationBarIconView.heightAnchor.constraint(equalTo: self.navigationBarIconView.widthAnchor)
])
let size = self.navigationBarTitleView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
self.navigationBarTitleView.bounds.size = size
self.navigationItem.titleView = self.navigationBarTitleView
self._backgroundBlurEffect = self.backgroundBlurView.effect as? UIBlurEffect
self._backgroundBlurTintColor = self.backgroundBlurView.contentView.backgroundColor
self.contentSizeObservation = self.contentViewController.scrollView.observe(\.contentSize, options: [.new, .old]) { [weak self] (scrollView, change) in
guard let size = change.newValue, let previousSize = change.oldValue, size != previousSize else { return }
self?.view.setNeedsLayout()
self?.view.layoutIfNeeded()
}
// Don't call update() before subclasses have finished viewDidLoad().
// self.update()
NotificationCenter.default.addObserver(self, selector: #selector(HeaderContentViewController.willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(HeaderContentViewController.didBecomeActive(_:)), name: UIApplication.didBecomeActiveNotification, object: nil)
if #available(iOS 15, *)
{
// Fix navigation bar + tab bar appearance on iOS 15.
self.setContentScrollView(self.scrollView)
}
// Start with navigation bar hidden.
self.hideNavigationBar()
self.view.tintColor = self.tintColor?.adjustedForDisplay
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
self.prepareBlur()
// Update blur immediately.
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
self.headerScrollView.flashScrollIndicators()
self.update()
}
override func viewIsAppearing(_ animated: Bool)
{
super.viewIsAppearing(animated)
// Ensure header view has correct layout dimensions.
self.headerView.setNeedsLayout()
}
override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)
self._shouldResetLayout = true
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
if self._shouldResetLayout
{
// Various events can cause UI to mess up, so reset affected components now.
self.prepareBlur()
// Reset navigation bar animation, and create a new one later in this method if necessary.
self.resetNavigationBarAnimation()
self._shouldResetLayout = false
}
let statusBarHeight: Double
if let navigationController, navigationController.presentingViewController != nil, navigationController.modalPresentationStyle != .fullScreen
{
statusBarHeight = 20
}
else if let statusBarManager = (self.view.window ?? self.presentedViewController?.view.window)?.windowScene?.statusBarManager
{
statusBarHeight = statusBarManager.statusBarFrame.height
}
else
{
statusBarHeight = 0
}
let cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
let inset = 15 as CGFloat
let padding = 20 as CGFloat
let backButtonSize = self.backButton.sizeThatFits(CGSize(width: Double.infinity, height: .infinity))
let largestBackButtonDimension = max(backButtonSize.width, backButtonSize.height) // Enforce 1:1 aspect ratio.
var backButtonFrame = CGRect(x: inset, y: statusBarHeight, width: largestBackButtonDimension, height: largestBackButtonDimension)
var headerFrame = CGRect(x: inset, y: 0, width: self.view.bounds.width - inset * 2, height: self.headerView.bounds.height)
var contentFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height)
var backgroundIconFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.width)
let backButtonPadding = 8.0
let minimumHeaderY = backButtonFrame.maxY + backButtonPadding
let minimumContentHeight = minimumHeaderY + headerFrame.height + padding // Minimum height for header + back button + spacing.
let maximumContentY = max(self.view.bounds.width * 0.667, minimumContentHeight) // Initial Y-value of content view.
contentFrame.origin.y = maximumContentY - self.scrollView.contentOffset.y
headerFrame.origin.y = contentFrame.origin.y - padding - headerFrame.height
// Stretch the app icon image to fill additional vertical space if necessary.
let height = max(contentFrame.origin.y + cornerRadius * 2, backgroundIconFrame.height)
backgroundIconFrame.size.height = height
// Update blur.
self.updateBlur()
// Animate navigation bar.
let showNavigationBarThreshold = (maximumContentY - minimumContentHeight) + backButtonFrame.origin.y
if self.scrollView.contentOffset.y > showNavigationBarThreshold
{
if self.navigationBarAnimator == nil
{
self.prepareNavigationBarAnimation()
}
let difference = self.scrollView.contentOffset.y - showNavigationBarThreshold
let range: Double
if self.presentingViewController == nil && self.parent?.presentingViewController == nil
{
// Not presented modally, so rely on safe area + navigation bar height.
range = (headerFrame.height + padding) - (self.navigationController?.navigationBar.bounds.height ?? self.view.safeAreaInsets.top)
}
else
{
// Presented modally, so rely on maximumContentY.
range = maximumContentY - (maximumContentY - padding - headerFrame.height) - inset
}
let fractionComplete = min(difference, range) / range
self.navigationBarAnimator?.fractionComplete = fractionComplete
}
else
{
self.navigationBarAnimator?.fractionComplete = 0.0
self.resetNavigationBarAnimation()
}
let beginMovingBackButtonThreshold = (maximumContentY - minimumContentHeight)
if self.scrollView.contentOffset.y > beginMovingBackButtonThreshold
{
let difference = self.scrollView.contentOffset.y - beginMovingBackButtonThreshold
backButtonFrame.origin.y -= difference
}
let pinContentToTopThreshold = maximumContentY
if self.scrollView.contentOffset.y > pinContentToTopThreshold
{
contentFrame.origin.y = 0
backgroundIconFrame.origin.y = 0
let difference = self.scrollView.contentOffset.y - pinContentToTopThreshold
self.contentViewController.scrollView.contentOffset.y = -self.contentViewController.scrollView.contentInset.top + difference
}
else
{
// Keep content table view's content offset at the top.
self.contentViewController.scrollView.contentOffset.y = -self.contentViewController.scrollView.contentInset.top
}
// Keep background app icon centered in gap between top of content and top of screen.
backgroundIconFrame.origin.y = (contentFrame.origin.y / 2) - backgroundIconFrame.height / 2
// Set frames.
self.contentViewController.view.frame = contentFrame
self.contentViewControllerShadowView.frame = contentFrame
self.backgroundImageView.frame = backgroundIconFrame
self.backButton.frame = backButtonFrame
self.backButton.layer.cornerRadius = backButtonFrame.height / 2
// Adjust header scroll view content size for paging
self.headerView.frame = CGRect(origin: .zero, size: headerFrame.size)
self.headerScrollView.frame = headerFrame
self.headerScrollView.contentSize = CGSize(width: headerFrame.width * 2, height: headerFrame.height)
self.scrollView.verticalScrollIndicatorInsets.top = statusBarHeight
self.headerScrollView.horizontalScrollIndicatorInsets.bottom = -12
// Adjust content offset + size.
let contentOffset = self.scrollView.contentOffset
var contentSize = self.contentViewController.scrollView.contentSize
contentSize.height += self.contentViewController.scrollView.contentInset.top + self.contentViewController.scrollView.contentInset.bottom
contentSize.height += maximumContentY
contentSize.height = max(contentSize.height, self.view.bounds.height + maximumContentY - (self.navigationController?.navigationBar.bounds.height ?? 0))
self.scrollView.contentSize = contentSize
self.scrollView.contentOffset = contentOffset
}
func update()
{
// Overridden by subclasses.
}
/// Cannot add @objc functions in extensions of generic types, so include them in main definition instead.
//MARK: Notifications
@objc private func willEnterForeground(_ notification: Notification)
{
guard let navigationController = self.navigationController, navigationController.topViewController == self else { return }
self._shouldResetLayout = true
self.view.setNeedsLayout()
}
@objc private func didBecomeActive(_ notification: Notification)
{
guard let navigationController = self.navigationController, navigationController.topViewController == self else { return }
// Fixes incorrect blur after app becomes inactive -> active again.
self._shouldResetLayout = true
self.view.setNeedsLayout()
}
//MARK: UIAdaptivePresentationControllerDelegate
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool
{
return false
}
//MARK: UIScrollViewDelegate
func scrollViewDidScroll(_ scrollView: UIScrollView)
{
switch scrollView
{
case self.scrollView: self.view.setNeedsLayout()
case self.headerScrollView:
// Do NOT call setNeedsLayout(), or else it will mess with scrolling.
self.headerScrollView.showsHorizontalScrollIndicator = false
self.updateBlur()
default: break
}
}
//MARK: UIGestureRecognizerDelegate
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool
{
// Ignore interactive back gesture when viewing header, which means returning `true` to enable ignoreBackGestureRecognizer.
let disableBackGesture = self.isViewingHeader
return disableBackGesture
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
{
return true
}
}
private extension HeaderContentViewController
{
func showNavigationBar()
{
self.navigationBarIconView.alpha = 1.0
self.navigationBarNameLabel.alpha = 1.0
self.navigationBarButton.alpha = 1.0
self.updateNavigationBarAppearance(isHidden: false)
if self.traitCollection.userInterfaceStyle == .dark
{
self._preferredStatusBarStyle = .lightContent
}
else
{
self._preferredStatusBarStyle = .default
}
if #unavailable(iOS 17)
{
self.navigationController?.setNeedsStatusBarAppearanceUpdate()
}
}
func hideNavigationBar()
{
self.navigationBarIconView.alpha = 0.0
self.navigationBarNameLabel.alpha = 0.0
self.navigationBarButton.alpha = 0.0
self.updateNavigationBarAppearance(isHidden: true)
self._preferredStatusBarStyle = .lightContent
if #unavailable(iOS 17)
{
self.navigationController?.setNeedsStatusBarAppearanceUpdate()
}
}
func updateNavigationBarAppearance(isHidden: Bool)
{
let barAppearance = self.navigationItem.standardAppearance as? NavigationBarAppearance ?? NavigationBarAppearance()
if isHidden
{
barAppearance.configureWithTransparentBackground()
barAppearance.ignoresUserInteraction = true
}
else
{
barAppearance.configureWithDefaultBackground()
barAppearance.ignoresUserInteraction = false
}
barAppearance.titleTextAttributes = [.foregroundColor: UIColor.clear]
let dynamicColor = UIColor { traitCollection in
var tintColor = self.tintColor ?? .altPrimary
if traitCollection.userInterfaceStyle == .dark && tintColor.isTooDark
{
tintColor = .white
}
else
{
tintColor = tintColor.adjustedForDisplay
}
return tintColor
}
let tintColor = isHidden ? UIColor.clear : dynamicColor
barAppearance.configureWithTintColor(tintColor)
self.navigationItem.standardAppearance = barAppearance
self.navigationItem.scrollEdgeAppearance = barAppearance
}
func prepareBlur()
{
if let animator = self.blurAnimator
{
animator.stopAnimation(true)
}
self.backgroundBlurView.effect = self._backgroundBlurEffect
self.backgroundBlurView.contentView.backgroundColor = self._backgroundBlurTintColor
self.blurAnimator = UIViewPropertyAnimator(duration: 1.0, curve: .linear) { [weak self] in
self?.backgroundBlurView.effect = nil
self?.backgroundBlurView.contentView.backgroundColor = .clear
}
self.blurAnimator?.startAnimation()
self.blurAnimator?.pauseAnimation()
}
func updateBlur()
{
// A full blur is too much for header, so we reduce the visible blur by 0.3, resulting in 70% blur.
let minimumBlurFraction = 0.3 as CGFloat
if self.isViewingHeader
{
let maximumX = self.headerScrollView.bounds.width
let fraction = self.headerScrollView.contentOffset.x / maximumX
let fractionComplete = (fraction * (1.0 - minimumBlurFraction)) + minimumBlurFraction
self.blurAnimator?.fractionComplete = fractionComplete
}
else if self.scrollView.contentOffset.y < 0
{
// Determine how much to lessen blur by.
let range = 75 as CGFloat
let difference = -self.scrollView.contentOffset.y
let fraction = min(difference, range) / range
let fractionComplete = (fraction * (1.0 - minimumBlurFraction)) + minimumBlurFraction
self.blurAnimator?.fractionComplete = fractionComplete
}
else
{
// Set blur to default.
self.blurAnimator?.fractionComplete = minimumBlurFraction
}
}
func prepareNavigationBarAnimation()
{
self.resetNavigationBarAnimation()
self.navigationBarAnimator = UIViewPropertyAnimator(duration: 1.0, curve: .linear) { [weak self] in
self?.showNavigationBar()
// Must call layoutIfNeeded() to animate appearance change.
self?.navigationController?.navigationBar.layoutIfNeeded()
self?.contentViewController.view.layer.cornerRadius = 0
}
self.navigationBarAnimator?.startAnimation()
self.navigationBarAnimator?.pauseAnimation()
self.update()
}
func resetNavigationBarAnimation()
{
guard self.navigationBarAnimator != nil else { return }
self.navigationBarAnimator?.stopAnimation(true)
self.navigationBarAnimator = nil
self.hideNavigationBar()
self.contentViewController.view.layer.cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
}
}

View File

@@ -1,94 +0,0 @@
//
// Keychain.swift
// AltStore
//
// Created by Riley Testut on 6/4/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
import KeychainAccess
import AltSign
class Keychain
{
static let shared = Keychain()
private let keychain = KeychainAccess.Keychain(service: "com.rileytestut.AltStore").accessibility(.afterFirstUnlock).synchronizable(true)
private init()
{
}
func reset()
{
self.appleIDEmailAddress = nil
self.appleIDPassword = nil
self.signingCertificatePrivateKey = nil
self.signingCertificateSerialNumber = nil
}
}
extension Keychain
{
var appleIDEmailAddress: String? {
get {
let emailAddress = try? self.keychain.get("appleIDEmailAddress")
return emailAddress
}
set {
self.keychain["appleIDEmailAddress"] = newValue
}
}
var appleIDPassword: String? {
get {
let password = try? self.keychain.get("appleIDPassword")
return password
}
set {
self.keychain["appleIDPassword"] = newValue
}
}
var signingCertificatePrivateKey: Data? {
get {
let privateKey = try? self.keychain.getData("signingCertificatePrivateKey")
return privateKey
}
set {
self.keychain[data: "signingCertificatePrivateKey"] = newValue
}
}
var signingCertificateSerialNumber: String? {
get {
let serialNumber = try? self.keychain.get("signingCertificateSerialNumber")
return serialNumber
}
set {
self.keychain["signingCertificateSerialNumber"] = newValue
}
}
var patreonAccessToken: String? {
get {
let accessToken = try? self.keychain.get("patreonAccessToken")
return accessToken
}
set {
self.keychain["patreonAccessToken"] = newValue
}
}
var patreonRefreshToken: String? {
get {
let refreshToken = try? self.keychain.get("patreonRefreshToken")
return refreshToken
}
set {
self.keychain["patreonRefreshToken"] = newValue
}
}
}

View File

@@ -10,11 +10,23 @@ import UIKit
import Roxas
class NavigationBar: UINavigationBar
class NavigationBarAppearance: UINavigationBarAppearance
{
@IBInspectable var automaticallyAdjustsItemPositions: Bool = true
// We sometimes need to ignore user interaction so
// we can tap items underneath the navigation bar.
var ignoresUserInteraction: Bool = false
private let backgroundColorView = UIView()
override func copy(with zone: NSZone? = nil) -> Any
{
let copy = super.copy(with: zone) as! NavigationBarAppearance
copy.ignoresUserInteraction = self.ignoresUserInteraction
return copy
}
}
class NavigationBar: UINavigationBar
{
@IBInspectable var automaticallyAdjustsItemPositions: Bool = true
override init(frame: CGRect)
{
@@ -32,31 +44,39 @@ class NavigationBar: UINavigationBar
private func initialize()
{
self.shadowImage = UIImage()
let standardAppearance = UINavigationBarAppearance()
standardAppearance.configureWithDefaultBackground()
standardAppearance.shadowColor = nil
let edgeAppearance = UINavigationBarAppearance()
edgeAppearance.configureWithOpaqueBackground()
edgeAppearance.backgroundColor = self.barTintColor
edgeAppearance.shadowColor = nil
if let tintColor = self.barTintColor
{
self.backgroundColorView.backgroundColor = tintColor
let textAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
// Top = -50 to cover status bar area above navigation bar on any device.
// Bottom = -1 to prevent a flickering gray line from appearing.
self.addSubview(self.backgroundColorView, pinningEdgesWith: UIEdgeInsets(top: -50, left: 0, bottom: -1, right: 0))
standardAppearance.backgroundColor = tintColor
standardAppearance.titleTextAttributes = textAttributes
standardAppearance.largeTitleTextAttributes = textAttributes
edgeAppearance.titleTextAttributes = textAttributes
edgeAppearance.largeTitleTextAttributes = textAttributes
}
else
{
self.barTintColor = .white
standardAppearance.backgroundColor = nil
}
self.scrollEdgeAppearance = edgeAppearance
self.standardAppearance = standardAppearance
}
override func layoutSubviews()
{
super.layoutSubviews()
if self.backgroundColorView.superview != nil
{
self.insertSubview(self.backgroundColorView, at: 1)
}
if self.automaticallyAdjustsItemPositions
{
// We can't easily shift just the back button up, so we shift the entire content view slightly.
@@ -67,4 +87,15 @@ class NavigationBar: UINavigationBar
}
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
{
if let appearance = self.topItem?.standardAppearance as? NavigationBarAppearance, appearance.ignoresUserInteraction
{
// Ignore touches.
return nil
}
return super.hitTest(point, with: event)
}
}

Some files were not shown because too many files have changed in this diff Show More