Compare commits

...

203 Commits

Author SHA1 Message Date
Joseph Mattello
c4c2d17ffc snapshot
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-04-02 02:28:12 -04:00
Joe Mattiello
2c829895c9 spm hide daemon behind ifdef 2023-03-10 21:11:39 -05:00
Joe Mattiello
5463f2b935 spm SideBackup builds, if we need it? 2023-03-10 20:56:50 -05:00
Joe Mattiello
d644ee7ab0 spm import fixes after merging Shared 2023-03-10 20:43:59 -05:00
Joe Mattiello
351d4fd631 spm: wdiget almost builds again 2023-03-10 20:28:52 -05:00
Joe Mattiello
128b180c1f spm: complex refactor, document of package 2023-03-10 19:32:33 -05:00
Joe Mattiello
1f2693bea6 Fix enum to objc core data for app permission 2023-03-10 19:32:32 -05:00
Joe Mattiello
452cf89c95 Fix bundle again for swift module 2023-03-10 19:32:32 -05:00
Joe Mattiello
90ac0fb025 fix bundle for coredata model 2023-03-10 19:32:32 -05:00
Joe Mattiello
10f5ee1548 fix sboard errors, and notification retain error 2023-03-10 19:32:32 -05:00
Joe Mattiello
478b30c8fd Fix module ref in storyboards and xibs 2023-03-10 19:32:31 -05:00
Joe Mattiello
207f6aac32 Fix some Bundle refs 2023-03-10 19:32:31 -05:00
Joe Mattiello
e1ed6f5ba3 Fix package.swift mistake 2023-03-10 19:32:31 -05:00
Joe Mattiello
444aac1210 Update paths for ci cd 2023-03-10 19:32:30 -05:00
Joe Mattiello
f49fa24743 App builds in xcodeproj (todo widget) 2023-03-10 19:31:01 -05:00
Joe Mattiello
4c9c5b1a56 XCode project for app, moved app project to folder 2023-03-10 19:30:59 -05:00
Joe Mattiello
365cadbb31 spm ignore unused resources 2023-03-10 19:30:20 -05:00
Joe Mattiello
36e03a52a7 Add some spm plugins to test 2023-03-10 19:30:20 -05:00
Joe Mattiello
19cf1722fa Use em_proxy and minimuxer from gh release 2023-03-10 19:30:19 -05:00
Joe Mattiello
c28a45f100 refactor to SideStoreAppKit 2023-03-10 19:30:19 -05:00
Joe Mattiello
df5b0c3af1 spm App target builds and links! 2023-03-10 19:30:17 -05:00
Joe Mattiello
8b1e87d2dd spm app builds 2023-03-10 19:30:17 -05:00
Joe Mattiello
e036f07875 spm the libs build 2023-03-10 19:30:16 -05:00
Joe Mattiello
2d232fa702 fix submodule 2023-03-10 19:30:15 -05:00
Joe Mattiello
686d1ab42a libimobiledev libs almost build 2023-03-10 19:30:15 -05:00
Joe Mattiello
d22d12c234 move submodules 2023-03-10 19:30:14 -05:00
Joe Mattiello
364b11ec9d More spm fixes 2023-03-10 19:30:14 -05:00
Joe Mattiello
f3a70e1e47 recreate legacy project for testing 2023-03-10 19:30:09 -05:00
Joe Mattiello
493b3783f0 Create swift package 2023-03-10 19:30:09 -05:00
Joe Mattiello
4669227567 mv AltBackup to Sources/ 2023-03-10 19:30:04 -05:00
Joseph Mattello
dfcc6e714e delete unused roxas submodule for spm
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-03-10 19:30:04 -05:00
Joseph Mattello
3b824eac96 UIColorHex fix deprecations
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-03-10 19:30:04 -05:00
Joseph Mattello
a6559d8bb9 Replace some Roxas with Roxas UI
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-03-10 19:30:03 -05:00
Joseph Mattello
f270ecc537 Replace local code with updated Roxas and SideKit
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-03-10 19:30:03 -05:00
naturecodevoid
4e84dc4cc8 [skip ci] rename the *.dSYM artifact so that macOS treats it as a normal folder 2023-03-07 08:24:28 -08:00
naturecodevoid
1a1ed072bf attach debugging symbols to actions builds 2023-03-07 07:50:31 -08:00
naturecodevoid
ae457f07c4 add https for ani.sidestore.io to Settings.bundle 2023-03-07 07:27:36 -08:00
Nythepegasus
00095942c3 Merge pull request #288 from SideStore/Remove-jk-anisette 2023-03-07 00:54:51 -05:00
Spidy123222
d1caa5fc21 Merge branch 'develop' into Remove-jk-anisette 2023-03-06 12:11:02 -08:00
Spidy123222
813e2f97ac 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
bcb5a90f5e Add SSL encryption to ani.sidestore.io
Signed-off-by: Nythepegasus <nythepegasus84@gmail.com>
2023-03-06 14:09:16 -05:00
Spidy123222
020a1a3149 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
c4d649ec58 Remove jkcoxson anisette servers.
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-03-05 13:45:10 -08:00
naturecodevoid
c02cf2c284 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
c30afd042e 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
17640fe6cf Cherry pick app ID logging fix from duplicate profiles PR 2023-02-25 14:36:37 -08:00
Joe Mattiello
2e4f6ee420 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
a3768d9221 [skip ci] actions: Add info/automate cache resetting 2023-02-21 17:27:56 -08:00
naturecodevoid
80c3390363 [skip ci] Makefile: Remove build_rust_dependencies 2023-02-21 17:07:58 -08:00
naturecodevoid
a5e3869d8f Project: update CONTRIBUTING.md to use Makefile 2023-02-21 17:01:24 -08:00
naturecodevoid
aa7d7c2d02 Revert "modify actions to work on test branch"
This reverts commit e59fb15926.
2023-02-21 12:51:34 -08:00
naturecodevoid
015f205569 update release descriptions 2023-02-21 12:42:56 -08:00
naturecodevoid
e59fb15926 modify actions to work on test branch 2023-02-21 12:24:25 -08:00
naturecodevoid
173c585f2d cleanup actions, revamp beta action, modify nightly build num system to be day specific 2023-02-21 12:23:12 -08:00
naturecodevoid
6f8c27793e cleanup makefile and add build steps from github actions 2023-02-21 12:19:08 -08:00
naturecodevoid
332b81c803 No more rust 2023-02-20 20:36:39 -08:00
naturecodevoid
4b343b500d fetch-prebuilt.sh whitespace improvement 2023-02-20 19:43:46 -08:00
naturecodevoid
e87c537642 Update CONTRIBUTING.md 2023-02-20 18:58:42 -08:00
naturecodevoid
2e6300cce2 add changes from attempt #1 2023-02-20 18:50:40 -08:00
naturecodevoid
09514d15a6 use prebuilt binaries 2023-02-20 18:48:21 -08:00
naturecodevoid
0de23dcba0 remove submodules 2023-02-20 16:33:11 -08:00
Joss Laymon
bacb153151 Use new pojav url
Signed-off-by: Joss Laymon <71040782+bogotesr@users.noreply.github.com>
2023-02-20 11:21:30 -07:00
naturecodevoid
a01aa299d8 SourcesViewController: Fix 1 trusted source causing an error making all trusted sources fail to load 2023-02-18 20:33:44 -08:00
Spidy123222
44edbddbd8 Replace placeholder video with instructions. (#266) 2023-02-15 21:14:51 -08:00
naturecodevoid
79d677cf3c 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
be39b6512f Add sidestore anisette server
Signed-off-by: oqammx86 <84847714+oq-x@users.noreply.github.com>
2023-02-02 13:05:36 -05:00
naturecodevoid
fcfeea35da Revert "Release channel support (#239)"
This reverts commit 7d0eb8c61e.
2023-02-02 08:09:15 -08:00
naturecodevoid
7d0eb8c61e 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
4d8438a6b6 Update minimuxer 2023-02-01 20:05:26 -08:00
naturecodevoid
f611244e35 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
546a978d3b Update .gitignore 2023-01-25 06:50:06 -08:00
naturecodevoid
70b23fb073 Merge remote-tracking branch 'upstream/develop' into develop 2023-01-19 17:39:11 -08:00
naturecodevoid
a56ca597d6 Fix build errors 2023-01-19 17:37:43 -08:00
naturecodevoid
679e0228a8 Remove debug
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-19 08:30:47 -08:00
naturecodevoid
e153394323 Merge branch 'develop' of https://github.com/naturecodevoid/SideStore into develop 2023-01-19 07:56:52 -08:00
naturecodevoid
5bd1fcfcfd Merge branch 'develop' of https://github.com/SideStore/SideStore into develop 2023-01-19 07:54:25 -08:00
naturecodevoid
2a392ddc44 SemVer version comparison 2023-01-19 07:52:47 -08:00
Joelle Stickney
b5cb8bc0d9 Updated Patreon Link 2023-01-18 14:41:24 -05:00
Joelle Stickney
fa170bcf98 Update README.md
Signed-off-by: Joelle Stickney <joellestickney+commit@gmail.com>
2023-01-13 10:39:12 -05:00
Joe Mattiello
7939d46949 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
ab9df8201a Merge remote-tracking branch 'upstream/develop' into develop 2023-01-09 17:40:50 -08:00
SoY0ung
4a670ec091 You can check minimuxer log in Error Log Page 2023-01-09 16:17:00 +08:00
SoY0ung
10e57e59c4 You can access SideStore document from File App 2023-01-09 15:19:18 +08:00
SoY0ung
b9ec43ef34 Add 'Reset Pairing File' 2023-01-09 15:15:31 +08:00
SoY0ung
42197cd375 Fix Advanced Setting display error 2023-01-09 14:13:31 +08:00
jawshoeadan
704852973b 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
056b4200df Update AltBackup.ipa 2023-01-04 21:55:00 -08:00
Joe Mattiello
250a7d8627 Merge pull request #213 from SideStore/feature/JoeMFixes
Joe M fixes for 1.0
2023-01-04 12:58:56 -05:00
Joseph Mattello
1ba51e161e Add placeholder for minimux retries
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 12:20:08 -05:00
Joseph Mattello
32e58af896 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
312fa6fe76 final classes marked as final
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:52:12 -05:00
Joseph Mattello
afbe0837ba 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
36ad2a720f log functions inlineable
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:31:51 -05:00
Joseph Mattello
901e3b14bb add final class to some classes
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:31:41 -05:00
Joseph Mattello
588d209f7b refs #160 codable feed structs
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:31:28 -05:00
naturecodevoid
554c54e6be 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
b0fac34ffc 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
5ede9f7c6b Remove testing logic, final changes
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
c7254fd23e Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
55fcea04af Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
c212c0a6b2 Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
a31fd6709a Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
e367fd2b73 Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
1ca67d0241 Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
8ffa952ff9 start on nightly builds
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
Joelle Stickney
da246fa30b Revert "Change default anisette server"
This reverts commit 25103c1188.
2023-01-04 08:52:59 -05:00
Joseph Mattello
13f306742e Revert "Merge pull request #189 from Nythepegasus/feature/retries"
This reverts commit 50841f5e24, reversing
changes made to d797ddd668.
2023-01-04 08:48:33 -05:00
Joelle Stickney
f3815dc45e Merge pull request #209 from SideStore/feature/deeplink_settings
Open Settings.app from SettingsVC
2023-01-01 12:33:31 -05:00
Joseph Mattello
d086254012 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
bc4d5ba097 Fix sidestore version
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-12-30 19:07:17 -08:00
Spidy123222
c556783fe3 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
5fba4c12aa 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
7e0dde3ece 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
fc03e83531 fix wrong libframentzip.a link target
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 17:09:44 -05:00
Nythepegasus
4c441077c7 Add a bunch more "logging" throughout signing 2022-12-30 17:09:44 -05:00
Nythepegasus
4a5ca81e9a Show file extensions to help user choose file 2022-12-30 17:09:44 -05:00
Nythepegasus
75eebe8f8c Use right Bundle ID for AltWidget 2022-12-30 17:09:44 -05:00
Nythepegasus
271a8cdac5 Change the Bundle ID to always be SideStore 2022-12-30 17:09:44 -05:00
Nythepegasus
25103c1188 Change default anisette server 2022-12-30 17:09:44 -05:00
Nythepegasus
d81058e606 Add our own Analytics key 2022-12-30 17:09:44 -05:00
Nythepegasus
693df54b3b Change version to be 0.1.2 2022-12-30 17:09:43 -05:00
Riley Testut
ae6ed99dc4 Displays “TODAY” as section header for logged errors that occured that day 2022-12-30 17:09:15 -05:00
Riley Testut
14bd58e741 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
6d35a7a4ba Fixes widgets potentially not updating after refreshing apps 2022-12-30 17:09:15 -05:00
Riley Testut
46b0d1ceac 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
67a66d2fcd Fixes ErrorLogViewController’s dark mode appearance 2022-12-30 17:09:15 -05:00
Riley Testut
43e90b57ea [Apps] Updates AltStore beta to 1.6b2 2022-12-30 17:09:14 -05:00
Riley Testut
c80740e590 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
54ccb9611e 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
8fcb897800 [Apps] Updates AltStore beta to 1.6b1 2022-12-30 17:09:14 -05:00
Riley Testut
699eda5d1b [AltWidget] Adds “icon” style lock screen widget 2022-12-30 17:09:14 -05:00
Riley Testut
d7d0a83550 [AltWidget] Replaces ProgressRing with SwiftUI.Gauge 2022-12-30 17:09:14 -05:00
Riley Testut
e3c331c911 [AltServer] Fixes potential race condition crash when managing connections 2022-12-30 17:09:14 -05:00
Riley Testut
eda4dd6aec Updates app version to 1.6b1 2022-12-30 17:09:14 -05:00
Riley Testut
8ad7be474d Resolves AppVersion context-level conflict after migrating from Core Data model v10 2022-12-30 17:09:14 -05:00
Riley Testut
a64435f155 Migrates Core Data model from v10 to v11 2022-12-30 17:09:14 -05:00
Riley Testut
fa160124d2 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
5765cb8330 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
f472b227bb Automatically purges LoggedErrors older than one month
Occurs whenever app enters background.
2022-12-30 17:09:14 -05:00
Riley Testut
d2b419c42e 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
09d4de660f Fixes CollapsingTextView incorrectly showing More button 2022-12-30 17:08:49 -05:00
Riley Testut
728dcd8523 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
93cf9bf6a9 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
50841f5e24 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
fc6d92d1fc Add retries in the various minimuxer functions 2022-12-30 17:04:19 -05:00
Nythepegasus
7162a029bb Retry AFC 10 times before giving up 2022-12-30 17:04:19 -05:00
Joseph Mattello
d797ddd668 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
989e8c3aa6 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
08b79af242 Merge pull request #192 from LitRitt/new-ui-color
Change UI Color
2022-12-30 16:56:54 -05:00
LitRitt
0d2f346a30 Changes default app tint color
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-30 15:59:33 -05:00
LitRitt
39f1d5f5fd 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
05008bb7f8 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
be90d6fc45 Changes settings highlight color
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-30 15:59:33 -05:00
LitRitt
a1bcdf9924 Change settings background color
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-30 15:59:33 -05:00
Joe Mattiello
b0e001393c 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
2d08941f6a udpate em_proxy.h
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:47:27 -05:00
Joseph Mattello
d0fef1f312 fix header paths
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:41:40 -05:00
Joseph Mattello
68342cb0d4 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
2b419212a7 replace fragmentzip.a with xcodeproj
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:23:06 -05:00
Joseph Mattello
b2cbc7e34d github action please work
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:17:55 -05:00
Joseph Mattello
61247e575b fix build for updated submodules
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:17:47 -05:00
Joseph Mattello
31e18266d1 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
df8a8de889 update libplist to head
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:57:54 -05:00
Joseph Mattello
8a037d6b29 update libmobiledevice to head
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:57:05 -05:00
Joseph Mattello
47b555b98c project: fix wrong paths for depends
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:56:52 -05:00
Joseph Mattello
0c2dae475e cargo: fix github action?
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:49:13 -05:00
Joseph Mattello
dc676d04d8 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
15b54bff50 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
47bd4b4c0b Update trustedapps.json
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-12-28 23:50:24 -05:00
Joelle Stickney
3c8b36ddfe Merge pull request #200 from SideStore/Add-Yattee-Source
Add Yattee to trusted sources
2022-12-25 05:42:05 -05:00
Spidy123222
608df3fddd 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
c092c285ee 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
93b745e379 More credit adjustments 2022-12-24 17:19:55 -05:00
Joelle Stickney
c18db77ade 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
2c0b167e6b Adjusted credits 2022-12-19 07:36:44 -05:00
Joelle Stickney
313254d0c8 Credit text changes 2022-12-19 06:40:10 -05:00
f1shy-dev
6f519c97d3 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
17a3e16b1d 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
8199358088 Info.plist add LSSupportsOpeningDocumentsInPlace 1
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 04:29:10 -05:00
Joseph Mattello
412928eeaa 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
51e1b935bd 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
742b51e5e2 fix deprecated style .white to .medium
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:12:13 -05:00
Joseph Mattello
fdb5e2eebb fix xcode warning
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:11:22 -05:00
Joseph Mattello
0192f64cd2 Add AltStore Release scheme
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:10:45 -05:00
Joseph Mattello
193298ac87 fix crash for missing patreon images
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:10:28 -05:00
Joe Mattiello
a81cb81799 Merge pull request #181 from LitRitt/change-designer
Update app icon and designer
2022-12-11 21:56:58 -05:00
LitRitt
ad8a7fdc9b Update Settings.storyboard
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-11 15:54:18 -08:00
LitRitt
5440afcebe Update SettingsViewController.swift
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-11 15:54:18 -08:00
LitRitt
715d7e664c Update App Icon
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-11 15:54:18 -08:00
jawshoeadan
aa182cfa68 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
f92dd7a872 Remove all restrictions based on Patreon account (hopefully) 2022-12-11 15:41:43 -06:00
Jawshoeadan
b02b9197d0 Fix beta apps on the my apps view, and fix deactivating/activating apps 2022-12-08 19:24:28 -06:00
Jawshoeadan
86d02be70c Remove Patreon check to show beta apps in store view 2022-12-08 10:28:34 -06:00
Spidy123222
cb990978ee add pokemmo into trusted sources. (#176) 2022-12-07 16:36:17 -08:00
Joshua Laymon
a103202c92 Merge pull request #171 from bogotesr/settings
Better anisette settings
2022-12-05 11:39:08 -07:00
bogotesr
9d7b133037 Add crystal's server 2022-12-04 15:43:08 -07:00
bogotesr
f727f2a1a9 describe what the toggle does 2022-12-03 21:19:31 -07:00
bogotesr
03034768d9 Better anisette settings
adds multivalue selector for some anisette servers
2022-12-03 13:48:33 -07:00
Joe Mattiello
aed3e20e08 Merge pull request #164 from SideStore/update-em-submodules 2022-12-01 20:02:04 -05:00
JJTech0130
74bac6d986 update em_proxy and minimuxer submodules 2022-11-30 18:17:01 -05:00
Fabian Thies
7ebecc353a 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
f0302b0d1e Add an issue template (#157)
Update issue templates
2022-11-26 19:34:52 -07:00
Spidy123222
0b004ad089 Update app.json
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 21:54:37 -08:00
528 changed files with 22484 additions and 15557 deletions

21
.codecov.yml Normal file
View File

@@ -0,0 +1,21 @@
# https://docs.codecov.io/docs/codecov-yaml
codecov:
require_ci_to_pass: true
coverage:
precision: 2
round: down
range: "70...100"
ignore:
- Dependencies
status:
patch:
default:
if_no_uploads: error
changes: true
project:
default:
target: auto
if_no_uploads: error
comment: false

View File

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

0
.env Normal file
View File

2
.github/CODEOWNERS vendored
View File

@@ -1 +1 @@
* @JoeMatt @lonkelle @jkcoxson
* @JoeMatt @lonkelle

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:
- naturecodevoid
body:
- type: markdown
attributes:
value: |
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/RgpFBX3Q3k) 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/RgpFBX3Q3k
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,33 @@
name: Feature Request
description: Suggest a feature
title: "[FEATURE REQUEST] "
labels: ["enhancement"]
assignees:
- naturecodevoid
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/RgpFBX3Q3k) 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.

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

@@ -0,0 +1,15 @@
### 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
<!-- If your PR doesn't close an issue, you can remove the next line. -->
Closes #1234

View File

@@ -0,0 +1,20 @@
# .github/workflows/sidestore-project.yml
name: SideStore Project
on:
push:
branches:
- main
pull_request:
jobs:
build:
name: Build
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: tuist/tuist-action@0.13.0
with:
command: 'build'
arguments: ''

View File

@@ -0,0 +1,22 @@
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

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

@@ -0,0 +1,90 @@
name: Beta SideStore build
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+' # example: 1.0.0-beta.1
jobs:
build:
name: Build and upload SideStore Beta
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-12'
version: '14.2'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies
run: brew install ldid
- name: Change version to tag
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: ${{ matrix.version }}
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
- name: Fakesign app
run: make fakesign
- name: Convert to IPA
run: make ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore.ipa
path: SideStore.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-dSYM
path: ./*.dSYM/
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Get current date
id: date
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
- name: Get current date in SideStore date form
id: date_sidestore
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Upload to new beta release
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ steps.version.outputs.version }}
tag_name: ${{ github.ref_name }}
draft: true
prerelease: true
files: SideStore.ipa
body: |
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
Beta builds are hand-picked builds from development commits that will allow you to try out new features earlier than normal. However, **they might contain bugs and other issues. Use at your own risk!**
## Changelog
- TODO
## Build Info
Built at (UTC): `${{ steps.date.outputs.date }}`
Built at (UTC date): `${{ steps.date_sidestore.outputs.date }}`
Commit SHA: `${{ github.sha }}`
Version: `${{ steps.version.outputs.version }}`

View File

@@ -1,100 +0,0 @@
name: Build and Upload SideStore
on:
push:
branches:
- master
- develop
pull_request:
jobs:
build:
name: Build and upload SideStore
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-12'
version: '14.0.0'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Cache rust cargo
id: cache-rust-cargo
uses: actions/cache@v3
env:
cache-name: cache-rust-cargo
with:
path: ~/.cargo
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Cache rust minimuxer
id: cache-rust-minimuxer
uses: actions/cache@v3
env:
cache-name: cache-rust-minimuxer
with:
path: ./Dependencies/minimuxer/target
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Cache rust em_proxy
id: cache-rust-em_proxy
uses: actions/cache@v3
env:
cache-name: cache-rust-em_proxy
with:
path: ./Dependencies/em_proxy/target
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Install dependencies
run: brew install ldid
- name: Install rustup
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
target: aarch64-apple-ios
- name: Create emotional damage
run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
- name: Build minimuxer
run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: ${{ matrix.version }}
- name: Build SideStore
run: |
rm -rf ~/Library/Developer/Xcode/DerivedData/
rm ./AltStore.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
xcodebuild -project AltStore.xcodeproj -scheme AltStore -sdk iphoneos archive -archivePath ./archive CODE_SIGNING_REQUIRED=NO AD_HOC_CODE_SIGNING_ALLOWED=YES CODE_SIGNING_ALLOWED=NO DEVELOPMENT_TEAM=XYZ0123456 ORG_IDENTIFIER=com.SideStore | xcpretty && exit ${PIPESTATUS[0]}
- name: Fakesign app
run: |
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
- name: Convert to IPA
run: |
mkdir Payload
mkdir Payload/SideStore.app
cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/
zip -r SideStore.ipa Payload
- name: Upload Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore.ipa
path: SideStore.ipa

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Ensure we are in root directory
cd "$(dirname "$0")/../.."
DATE=`date -u +'%Y.%m.%d'`
BUILD_NUM=1
write() {
sed -e "/MARKETING_VERSION = .*/s/$/-nightly.$DATE.$BUILD_NUM/" -i '' Build.xcconfig
echo "$DATE,$BUILD_NUM" > .nightly-build-num
}
if [ ! -f ".nightly-build-num" ]; then
write
exit 0
fi
LAST_DATE=`cat .nightly-build-num | perl -n -e '/([^,]*),([^ ]*)$/ && print $1'`
LAST_BUILD_NUM=`cat .nightly-build-num | perl -n -e '/([^,]*),([^ ]*)$/ && print $2'`
if [[ "$DATE" != "$LAST_DATE" ]]; then
write
else
BUILD_NUM=`expr $LAST_BUILD_NUM + 1`
write
fi

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

@@ -0,0 +1,100 @@
name: Nightly SideStore build
on:
push:
branches:
- develop
jobs:
build:
name: Build and upload SideStore Nightly
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-12'
version: '14.2'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies
run: brew install ldid
- name: Cache .nightly-build-num
uses: actions/cache@v3
with:
path: .nightly-build-num
key: nightly-build-num
- name: Increase nightly build number and set as version
run: bash .github/workflows/increase-nightly-build-num.sh
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: ${{ matrix.version }}
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
- name: Fakesign app
run: make fakesign
- name: Convert to IPA
run: make ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore.ipa
path: SideStore.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-dSYM
path: ./*.dSYM/
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Get current date
id: date
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
- name: Get current date in SideStore date form
id: date_sidestore
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Upload to nightly release
uses: IsaacShelton/update-existing-release@v1.3.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
release: "Nightly"
tag: "nightly"
prerelease: true
files: SideStore.ipa
body: |
This is an ⚠️ **EXPERIMENTAL** ⚠️ nightly build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
Nightly builds are **extremely experimental builds only meant to be used by developers and alpha testers. They often contain bugs and experimental features. Use at your own risk!**
If you want to try out new features early but want a lower chance of bugs, you can look at [SideStore Beta](https://github.com/${{ github.repository }}/releases?q=beta).
## Build Info
Built at (UTC): `${{ steps.date.outputs.date }}`
Built at (UTC date): `${{ steps.date_sidestore.outputs.date }}`
Commit SHA: `${{ github.sha }}`
Version: `${{ steps.version.outputs.version }}`
- name: Reset cache for apps.sidestore.io/nightly
run: sleep 10 && curl https://apps.sidestore.io/reset-cache/nightly/${{ secrets.SIDESOURCE_KEY }}

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

@@ -0,0 +1,52 @@
name: Pull Request SideStore build
on:
pull_request:
jobs:
build:
name: Build and upload SideStore
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-12'
version: '14.2'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies
run: brew install ldid
- name: Add PR suffix to version
run: sed -e '/MARKETING_VERSION = .*/s/$/-pr.${{ github.event.pull_request.number }}/' -i '' Build.xcconfig
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: ${{ matrix.version }}
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
- name: Fakesign app
run: make fakesign
- name: Convert to IPA
run: make ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore.ipa
path: SideStore.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-dSYM
path: ./*.dSYM/

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

@@ -0,0 +1,87 @@
name: Stable SideStore build
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
jobs:
build:
name: Build and upload SideStore
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-12'
version: '14.2'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies
run: brew install ldid
- name: Change version to tag
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: ${{ matrix.version }}
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
- name: Fakesign app
run: make fakesign
- name: Convert to IPA
run: make ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore.ipa
path: SideStore.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-dSYM
path: ./*.dSYM/
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Get current date
id: date
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
- name: Get current date in SideStore date form
id: date_sidestore
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Upload to new stable release
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ steps.version.outputs.version }}
tag_name: ${{ github.ref_name }}
draft: true
files: SideStore.ipa
body: |
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
## Changelog
- TODO
## Build Info
Built at (UTC): `${{ steps.date.outputs.date }}`
Built at (UTC date): `${{ steps.date_sidestore.outputs.date }}`
Commit SHA: `${{ github.sha }}`
Version: `${{ steps.version.outputs.version }}`

12
.gitignore vendored
View File

@@ -33,4 +33,14 @@ xcuserdata
/.vscode
## AppCode specific
.idea/
.idea/
Payload/
SideStore.ipa
*.dSYM
Dependencies/.*-prebuilt-fetch-*
Dependencies/minimuxer/*
Dependencies/em_proxy/*
!Dependencies/**/.gitkeep
.nightly-build-num

26
.gitmodules vendored
View File

@@ -1,24 +1,6 @@
[submodule "Dependencies/Roxas"]
path = Dependencies/Roxas
url = https://github.com/rileytestut/Roxas.git
[submodule "Dependencies/libimobiledevice"]
path = Dependencies/libimobiledevice
url = https://github.com/libimobiledevice/libimobiledevice
[submodule "Dependencies/libusbmuxd"]
path = Dependencies/libusbmuxd
url = https://github.com/libimobiledevice/libusbmuxd.git
[submodule "Dependencies/libplist"]
path = Dependencies/libplist
url = https://github.com/libimobiledevice/libplist.git
[submodule "Dependencies/MarkdownAttributedString"]
path = Dependencies/MarkdownAttributedString
url = https://github.com/chockenberry/MarkdownAttributedString.git
[submodule "Dependencies/em_proxy"]
path = Dependencies/em_proxy
url = https://github.com/jkcoxson/em_proxy
[submodule "Dependencies/libimobiledevice-glue"]
path = Dependencies/libimobiledevice-glue
url = https://github.com/libimobiledevice/libimobiledevice-glue
path = SideStoreApp/Dependencies/em_proxy
url = https://github.com/SideStore/em_proxy.git
[submodule "Dependencies/minimuxer"]
path = Dependencies/minimuxer
url = https://github.com/jkcoxson/minimuxer
path = SideStoreApp/Dependencies/minimuxer
url = https://github.com/SideStore/minimuxer.git

28
.jazzy.yaml Normal file
View File

@@ -0,0 +1,28 @@
# ---- About ----
module: SideStore
module_version: 1.0,0
author: SideStore
readme: README.md
copyright: 'See [license](https://github.com/SideStore/SideStore/blob/develop/LICENSE) for more details.'
# ---- URLs ----
author_url: https://sidestore.io
dash_url: https://sidestore.io/docsets/SideStore.xml
github_url: https://github.com/SideStore/SideStore/
github_file_prefix: https://github.com/SideStore/SideStore/tree/1.0.2/
# ---- Sources ----
source_directory: Sources
documentation: .build/x86_64-apple-macosx/debug/SideStore.docc
# ---- Generation ----
clean: true
output: docs
min_acl: public
hide_documentation_coverage: false
skip_undocumented: false
objc: false
swift_version: 5.1.0
# ---- Formatting ----
theme: fullwidth

42
.swiftformat Normal file
View File

@@ -0,0 +1,42 @@
# .swiftformat
## file options
--exclude .build,.github,.swiftpm,.vscode,Configurations,Dependencies
## format options
--allman false
--binarygrouping 4,8
--commas always
--comments indent
--decimalgrouping 3,6
--elseposition same-line
--empty void
--exponentcase lowercase
--exponentgrouping disabled
--fractiongrouping disabled
--header ignore
--hexgrouping 4,8
--hexliteralcase uppercase
--ifdef indent
--importgrouping testable-bottom
--indent 4
--indentcase false
--linebreaks lf
--maxwidth none
--octalgrouping 4,8
--operatorfunc spaced
--patternlet hoist
--ranges spaced
--self remove
--semicolons inline
--stripunusedargs always
--swiftversion 5.1
--trimwhitespace always
--wraparguments preserve
--wrapcollections preserve
## rules
--enable isEmpty,andOperator,assertionFailures

76
.swiftlint.yml Normal file
View File

@@ -0,0 +1,76 @@
disabled_rules:
- block_based_kvo
- colon
- control_statement
- cyclomatic_complexity
- discarded_notification_center_observer
- file_length
- function_parameter_count
- generic_type_name
- identifier_name
- multiple_closures_with_trailing_closure
- nesting
- switch_case_alignment
- todo
- type_name
- type_body_length
- function_body_length
- unused_closure_parameter
# parameterized rules can be customized from this configuration file
line_length: 200
# parameterized rules are first parameterized as a warning level, then error level.
type_body_length:
- 300 # warning
- 600 # error
# parameterized rules are first parameterized as a warning level, then error level.
# identifier_name_max_length:
# - 40 # warning
# - 60 # error
# # parameterized rules are first parameterized as a warning level, then error level.
# identifier_name_min_length:
# - 3 # warning
# - 2 # error
function_body_length:
- 200 # warning
- 500 # error
large_tuple:
- 4 # warning
- 6 # error
opt_in_rules:
- empty_count
- force_unwrapping
excluded: # paths to ignore during linting. overridden byincluded.
- .build
- .github
- .swiftpm
- .vscode
- Dependencies
analyzer_rules: # Rules run by `swiftlint analyze` (experimental)
- explicit_self
# Override these rules to be warnings for now
force_cast: warning
force_try: warning
empty_count: warning
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit)
custom_rules:
placeholders_in_comments:
included: ".*\\.swift"
name: "No Placeholders in Comments"
regex: "<#([^#]+)#>"
match_kinds:
- comment
- doccomment
message: "Placeholder left in comment."
tiles_deprecated:
included: ".*\\.swift"
name: "Tiles are deprecated in favor of Frame"
regex: "([T,t]ile$|^[T,t]il[e,es])"
message: "Tiles are deprecated in favor of Frame"
severity: warning

View File

@@ -1,3 +0,0 @@
#include "Build.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER).AltBackup

View File

@@ -1,206 +0,0 @@
//
// 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()
#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()
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])
}
}
@objc func didEnterBackground(_ notification: Notification)
{
// Reset UI once we've left app (but not before).
self.currentOperation = nil
}
}

View File

@@ -1,3 +0,0 @@
#include "Build.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER = $(ORG_PREFIX).$(PRODUCT_NAME)

View File

@@ -1,8 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "NSAttributedString+Markdown.h"
#import "ALTAppPatcher.h"
#include "fragmentzip.h"

View File

@@ -1,570 +0,0 @@
//
// AppViewController.swift
// AltStore
//
// Created by Riley Testut on 7/22/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
import AltStoreCore
import Roxas
import Nuke
class AppViewController: UIViewController
{
var app: StoreApp!
private var contentViewController: AppContentViewController!
private var contentViewControllerShadowView: UIView!
private var blurAnimator: UIViewPropertyAnimator?
private var navigationBarAnimator: UIViewPropertyAnimator?
private var contentSizeObservation: NSKeyValueObservation?
@IBOutlet private var scrollView: UIScrollView!
@IBOutlet private var contentView: UIView!
@IBOutlet private var bannerView: AppBannerView!
@IBOutlet private var backButton: UIButton!
@IBOutlet private var backButtonContainerView: UIVisualEffectView!
@IBOutlet private var backgroundAppIconImageView: UIImageView!
@IBOutlet private var backgroundBlurView: UIVisualEffectView!
@IBOutlet private var navigationBarTitleView: UIView!
@IBOutlet private var navigationBarDownloadButton: PillButton!
@IBOutlet private var navigationBarAppIconImageView: UIImageView!
@IBOutlet private var navigationBarAppNameLabel: UILabel!
private var _shouldResetLayout = false
private var _backgroundBlurEffect: UIBlurEffect?
private var _backgroundBlurTintColor: UIColor?
private var _preferredStatusBarStyle: UIStatusBarStyle = .default
override var preferredStatusBarStyle: UIStatusBarStyle {
return _preferredStatusBarStyle
}
override func viewDidLoad()
{
super.viewDidLoad()
self.navigationBarTitleView.sizeToFit()
self.navigationItem.titleView = self.navigationBarTitleView
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.contentViewController.view.superview?.insertSubview(self.contentViewControllerShadowView, at: 0)
self.contentView.addGestureRecognizer(self.scrollView.panGestureRecognizer)
self.contentViewController.view.layer.cornerRadius = 38
self.contentViewController.view.layer.masksToBounds = true
self.contentViewController.tableView.panGestureRecognizer.require(toFail: self.scrollView.panGestureRecognizer)
self.contentViewController.tableView.showsVerticalScrollIndicator = false
// Bring to front so the scroll indicators are visible.
self.view.bringSubviewToFront(self.scrollView)
self.scrollView.isUserInteractionEnabled = false
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.configure(for: self.app)
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
self.contentSizeObservation = self.contentViewController.tableView.observe(\.contentSize) { [weak self] (tableView, change) in
self?.view.setNeedsLayout()
self?.view.layoutIfNeeded()
}
self.update()
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.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
{
imageView?.isIndicatingActivity = false
}
}
}
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
self.prepareBlur()
// Update blur immediately.
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
self.transitionCoordinator?.animate(alongsideTransition: { (context) in
self.hideNavigationBar()
}, completion: nil)
}
override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)
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)
if self.navigationController == nil
{
self.resetNavigationBarAnimation()
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
guard segue.identifier == "embedAppContentViewController" else { return }
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)
self.navigationItem.scrollEdgeAppearance = self.navigationController?.navigationBar.standardAppearance
}
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
if self._shouldResetLayout
{
// 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.
self.resetNavigationBarAnimation()
self._shouldResetLayout = false
}
let statusBarHeight = UIApplication.shared.statusBarFrame.height
let cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
let inset = 12 as CGFloat
let padding = 20 as CGFloat
let backButtonSize = self.backButton.sizeThatFits(CGSize(width: 1000, height: 1000))
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.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)
let minimumHeaderY = backButtonFrame.maxY + 8
let minimumContentY = minimumHeaderY + headerFrame.height + padding
let maximumContentY = self.view.bounds.width * 0.667
// A full blur is too much, so we reduce the visible blur by 0.3, resulting in 70% blur.
let minimumBlurFraction = 0.3 as CGFloat
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
let blurThreshold = 0 as CGFloat
if self.scrollView.contentOffset.y < blurThreshold
{
// 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
}
// Animate navigation bar.
let showNavigationBarThreshold = (maximumContentY - minimumContentY) + backButtonFrame.origin.y
if self.scrollView.contentOffset.y > showNavigationBarThreshold
{
if self.navigationBarAnimator == nil
{
self.prepareNavigationBarAnimation()
}
let difference = self.scrollView.contentOffset.y - showNavigationBarThreshold
let range = (headerFrame.height + padding) - (self.navigationController?.navigationBar.bounds.height ?? self.view.safeAreaInsets.top)
let fractionComplete = min(difference, range) / range
self.navigationBarAnimator?.fractionComplete = fractionComplete
}
else
{
self.resetNavigationBarAnimation()
}
let beginMovingBackButtonThreshold = (maximumContentY - minimumContentY)
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.tableView.contentOffset.y = difference
}
else
{
// Keep content table view's content offset at the top.
self.contentViewController.tableView.contentOffset.y = 0
}
// 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.superview?.frame = contentFrame
self.bannerView.frame = headerFrame
self.backgroundAppIconImageView.frame = backgroundIconFrame
self.backgroundBlurView.frame = backgroundIconFrame
self.backButtonContainerView.frame = backButtonFrame
self.contentViewControllerShadowView.frame = self.contentViewController.view.frame
self.backButtonContainerView.layer.cornerRadius = self.backButtonContainerView.bounds.midY
self.scrollView.scrollIndicatorInsets.top = statusBarHeight
// Adjust content offset + size.
let contentOffset = self.scrollView.contentOffset
var contentSize = self.contentViewController.tableView.contentSize
contentSize.height += maximumContentY
self.scrollView.contentSize = contentSize
self.scrollView.contentOffset = contentOffset
self.bannerView.backgroundEffectView.backgroundColor = .clear
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
{
super.traitCollectionDidChange(previousTraitCollection)
self._shouldResetLayout = true
}
deinit
{
self.blurAnimator?.stopAnimation(true)
self.navigationBarAnimator?.stopAnimation(true)
}
}
extension AppViewController
{
class func makeAppViewController(app: StoreApp) -> AppViewController
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let appViewController = storyboard.instantiateViewController(withIdentifier: "appViewController") as! AppViewController
appViewController.app = app
return appViewController
}
}
private extension AppViewController
{
func 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)
}
else
{
button.setTitle(NSLocalizedString("OPEN", comment: ""), for: .normal)
}
let progress = AppManager.shared.installationProgress(for: self.app)
button.progress = progress
}
if Date() < self.app.versionDate
{
self.bannerView.button.countdownDate = self.app.versionDate
self.navigationBarDownloadButton.countdownDate = self.app.versionDate
}
else
{
self.bannerView.button.countdownDate = nil
self.navigationBarDownloadButton.countdownDate = nil
}
let barButtonItem = self.navigationItem.rightBarButtonItem
self.navigationItem.rightBarButtonItem = nil
self.navigationItem.rightBarButtonItem = barButtonItem
}
func showNavigationBar(for navigationController: UINavigationController? = nil)
{
let navigationController = navigationController ?? self.navigationController
navigationController?.navigationBar.alpha = 1.0
navigationController?.navigationBar.tintColor = .altPrimary
navigationController?.navigationBar.setNeedsLayout()
if self.traitCollection.userInterfaceStyle == .dark
{
self._preferredStatusBarStyle = .lightContent
}
else
{
self._preferredStatusBarStyle = .default
}
navigationController?.setNeedsStatusBarAppearanceUpdate()
}
func hideNavigationBar(for navigationController: UINavigationController? = nil)
{
let navigationController = navigationController ?? self.navigationController
navigationController?.navigationBar.alpha = 0.0
self._preferredStatusBarStyle = .lightContent
navigationController?.setNeedsStatusBarAppearanceUpdate()
}
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 prepareNavigationBarAnimation()
{
self.resetNavigationBarAnimation()
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
self?.contentViewController.view.layer.cornerRadius = 0
}
self.navigationBarAnimator?.startAnimation()
self.navigationBarAnimator?.pauseAnimation()
self.update()
}
func resetNavigationBarAnimation()
{
self.navigationBarAnimator?.stopAnimation(true)
self.navigationBarAnimator = nil
self.hideNavigationBar()
self.contentViewController.view.layer.cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
}
}
extension AppViewController
{
@IBAction func popViewController(_ sender: UIButton)
{
self.navigationController?.popViewController(animated: true)
}
@IBAction func performAppAction(_ sender: PillButton)
{
if let installedApp = self.app.installedApp
{
self.open(installedApp)
}
else
{
self.downloadApp()
}
}
func downloadApp()
{
guard self.app.installedApp == nil else { return }
let group = AppManager.shared.install(self.app, presentingViewController: self) { (result) in
do
{
_ = try result.get()
}
catch OperationError.cancelled
{
// Ignore
}
catch
{
DispatchQueue.main.async {
let toastView = ToastView(error: error)
toastView.show(in: self)
}
}
DispatchQueue.main.async {
self.bannerView.button.progress = nil
self.navigationBarDownloadButton.progress = nil
self.update()
}
}
self.bannerView.button.progress = group.progress
self.navigationBarDownloadButton.progress = group.progress
}
func open(_ installedApp: InstalledApp)
{
UIApplication.shared.open(installedApp.openAppURL)
}
}
private extension AppViewController
{
@objc func didChangeApp(_ notification: Notification)
{
// Async so that AppManager.installationProgress(for:) is nil when we update.
DispatchQueue.main.async {
self.update()
}
}
@objc func willEnterForeground(_ notification: Notification)
{
guard let navigationController = self.navigationController, navigationController.topViewController == self else { return }
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
{
func scrollViewDidScroll(_ scrollView: UIScrollView)
{
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
}

View File

@@ -1,241 +0,0 @@
//
// AppIDsViewController.swift
// AltStore
//
// Created by Riley Testut on 1/27/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import UIKit
import AltStoreCore
import Roxas
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! BannerCollectionViewCell
cell.layoutMargins.left = self.view.layoutMargins.left
cell.layoutMargins.right = self.view.layoutMargins.right
cell.tintColor = tintColor
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 numberOfDays = expirationDate.numberOfCalendarDays(since: currentDate)
let numberOfDaysText = (numberOfDays == 1) ? NSLocalizedString("1 day", comment: "") : String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
attributedAccessibilityLabel.mutableString.append(String(format: NSLocalizedString("Expires in %@.", comment: ""), numberOfDaysText) + " ")
}
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
let attributedBundleIdentifier = NSMutableAttributedString(string: appID.bundleIdentifier.lowercased(), attributes: [.accessibilitySpeechPunctuation: true])
if let team = appID.team, let range = attributedBundleIdentifier.string.range(of: team.identifier.lowercased()), #available(iOS 13, *)
{
// 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.layoutFittingExpandedSize.height),
withHorizontalFittingPriority: .required, // Width is fixed
verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed
return size
}
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

@@ -1,171 +0,0 @@
//
// AuthenticationViewController.swift
// AltStore
//
// Created by Riley Testut on 9/5/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
import AltSign
class AuthenticationViewController: UIViewController
{
var authenticationHandler: ((String, String, @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void) -> Void)?
var completionHandler: (((ALTAccount, ALTAppleAPISession, String)?) -> Void)?
private weak var toastView: ToastView?
@IBOutlet private var appleIDTextField: UITextField!
@IBOutlet private var passwordTextField: UITextField!
@IBOutlet private var signInButton: UIButton!
@IBOutlet private var appleIDBackgroundView: UIView!
@IBOutlet private var passwordBackgroundView: UIView!
@IBOutlet private var scrollView: UIScrollView!
@IBOutlet private var contentStackView: UIStackView!
override func viewDidLoad()
{
super.viewDidLoad()
self.signInButton.activityIndicatorView.style = .white
for view in [self.appleIDBackgroundView!, self.passwordBackgroundView!, self.signInButton!]
{
view.clipsToBounds = true
view.layer.cornerRadius = 16
}
if UIScreen.main.isExtraCompactHeight
{
self.contentStackView.spacing = 20
}
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationViewController.textFieldDidChangeText(_:)), name: UITextField.textDidChangeNotification, object: self.appleIDTextField)
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationViewController.textFieldDidChangeText(_:)), name: UITextField.textDidChangeNotification, object: self.passwordTextField)
self.update()
}
override func viewDidDisappear(_ animated: Bool)
{
super.viewDidDisappear(animated)
self.signInButton.isIndicatingActivity = false
self.toastView?.dismiss()
}
}
private extension AuthenticationViewController
{
func update()
{
if let _ = self.validate()
{
self.signInButton.isEnabled = true
self.signInButton.alpha = 1.0
}
else
{
self.signInButton.isEnabled = false
self.signInButton.alpha = 0.6
}
}
func validate() -> (String, String)?
{
guard
let emailAddress = self.appleIDTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines), !emailAddress.isEmpty,
let password = self.passwordTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines), !password.isEmpty
else { return nil }
return (emailAddress, password)
}
}
private extension AuthenticationViewController
{
@IBAction func authenticate()
{
guard let (emailAddress, password) = self.validate() else { return }
self.appleIDTextField.resignFirstResponder()
self.passwordTextField.resignFirstResponder()
self.signInButton.isIndicatingActivity = true
self.authenticationHandler?(emailAddress, password) { (result) in
switch result
{
case .failure(ALTAppleAPIError.requiresTwoFactorAuthentication):
// Ignore
DispatchQueue.main.async {
self.signInButton.isIndicatingActivity = false
}
case .failure(let error as NSError):
DispatchQueue.main.async {
let error = error.withLocalizedFailure(NSLocalizedString("Failed to Log In", comment: ""))
let toastView = ToastView(error: error)
toastView.textLabel.textColor = .altPink
toastView.detailTextLabel.textColor = .altPink
toastView.show(in: self)
self.toastView = toastView
self.signInButton.isIndicatingActivity = false
}
case .success((let account, let session)):
self.completionHandler?((account, session, password))
}
DispatchQueue.main.async {
self.scrollView.setContentOffset(CGPoint(x: 0, y: -self.view.safeAreaInsets.top), animated: true)
}
}
}
@IBAction func cancel(_ sender: UIBarButtonItem)
{
self.completionHandler?(nil)
}
}
extension AuthenticationViewController: UITextFieldDelegate
{
func textFieldShouldReturn(_ textField: UITextField) -> Bool
{
switch textField
{
case self.appleIDTextField: self.passwordTextField.becomeFirstResponder()
case self.passwordTextField: self.authenticate()
default: break
}
self.update()
return false
}
func textFieldDidBeginEditing(_ textField: UITextField)
{
guard UIScreen.main.isExtraCompactHeight else { return }
// Position all the controls within visible frame.
var contentOffset = self.scrollView.contentOffset
contentOffset.y = 44
self.scrollView.setContentOffset(contentOffset, animated: true)
}
}
extension AuthenticationViewController
{
@objc func textFieldDidChangeText(_ notification: Notification)
{
self.update()
}
}

View File

@@ -1,54 +0,0 @@
//
// InstructionsViewController.swift
// AltStore
//
// Created by Riley Testut on 9/6/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
class InstructionsViewController: UIViewController
{
var completionHandler: (() -> Void)?
var showsBottomButton: Bool = false
@IBOutlet private var contentStackView: UIStackView!
@IBOutlet private var dismissButton: UIButton!
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewDidLoad()
{
super.viewDidLoad()
if UIScreen.main.isExtraCompactHeight
{
self.contentStackView.layoutMargins.top = 0
self.contentStackView.layoutMargins.bottom = self.contentStackView.layoutMargins.left
}
self.dismissButton.clipsToBounds = true
self.dismissButton.layer.cornerRadius = 16
if self.showsBottomButton
{
self.navigationItem.hidesBackButton = true
}
else
{
self.dismissButton.isHidden = true
}
}
}
private extension InstructionsViewController
{
@IBAction func dismiss()
{
self.completionHandler?()
}
}

View File

@@ -1,61 +0,0 @@
//
// 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
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,44 +0,0 @@
//
// ScreenshotCollectionViewCell.swift
// AltStore
//
// Created by Riley Testut on 7/15/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
import Roxas
@objc(ScreenshotCollectionViewCell)
class ScreenshotCollectionViewCell: UICollectionViewCell
{
let imageView = UIImageView(image: nil)
override init(frame: CGRect)
{
super.init(frame: frame)
self.initialize()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
self.initialize()
}
private func initialize()
{
self.imageView.layer.masksToBounds = true
self.addSubview(self.imageView, pinningEdgesWith: .zero)
}
override func layoutSubviews()
{
super.layoutSubviews()
self.imageView.layer.cornerRadius = 4
}
}

View File

@@ -1,144 +0,0 @@
//
// AppBannerView.swift
// AltStore
//
// Created by Riley Testut on 8/29/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
import AltStoreCore
import Roxas
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 }
}
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 backgroundEffectView: UIVisualEffectView!
@IBOutlet private var vibrancyView: UIVisualEffectView!
@IBOutlet private var accessibilityView: UIView!
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
}
override func tintColorDidChange()
{
super.tintColorDidChange()
if self.tintAdjustmentMode != .dimmed
{
self.originalTintColor = self.tintColor
}
self.update()
}
}
extension AppBannerView
{
func configure(for app: AppProtocol)
{
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 storeApp.isBeta
{
self.name = String(format: NSLocalizedString("%@ beta", comment: ""), app.name)
self.isBeta = true
}
}
}
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
}
}
}
private extension AppBannerView
{
func update()
{
self.clipsToBounds = true
self.layer.cornerRadius = 22
self.subtitleLabel.textColor = self.originalTintColor ?? self.tintColor
self.backgroundEffectView.backgroundColor = self.originalTintColor ?? self.tintColor
}
}

View File

@@ -1,43 +0,0 @@
//
// AppIconImageView.swift
// AltStore
//
// Created by Riley Testut on 5/9/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
class AppIconImageView: UIImageView
{
override func awakeFromNib()
{
super.awakeFromNib()
self.contentMode = .scaleAspectFill
self.clipsToBounds = true
self.backgroundColor = .white
if #available(iOS 13, *)
{
self.layer.cornerCurve = .continuous
}
else
{
if self.layer.responds(to: Selector(("continuousCorners")))
{
self.layer.setValue(true, forKey: "continuousCorners")
}
}
}
override func layoutSubviews()
{
super.layoutSubviews()
// Based off of 60pt icon having 12pt radius.
let radius = self.bounds.height / 5
self.layer.cornerRadius = radius
}
}

View File

@@ -1,117 +0,0 @@
//
// CollapsingTextView.swift
// AltStore
//
// Created by Riley Testut on 7/23/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
class CollapsingTextView: UITextView
{
var isCollapsed = true {
didSet {
self.setNeedsLayout()
}
}
var maximumNumberOfLines = 2 {
didSet {
self.setNeedsLayout()
}
}
var lineSpacing: CGFloat = 2 {
didSet {
self.setNeedsLayout()
}
}
let moreButton = UIButton(type: .system)
override func awakeFromNib()
{
super.awakeFromNib()
self.layoutManager.delegate = self
self.textContainerInset = .zero
self.textContainer.lineFragmentPadding = 0
self.textContainer.lineBreakMode = .byTruncatingTail
self.textContainer.heightTracksTextView = true
self.textContainer.widthTracksTextView = true
self.moreButton.setTitle(NSLocalizedString("More", comment: ""), for: .normal)
self.moreButton.addTarget(self, action: #selector(CollapsingTextView.toggleCollapsed(_:)), for: .primaryActionTriggered)
self.addSubview(self.moreButton)
self.setNeedsLayout()
}
override func layoutSubviews()
{
super.layoutSubviews()
guard let font = self.font else { return }
let buttonFont = UIFont.systemFont(ofSize: font.pointSize, weight: .medium)
self.moreButton.titleLabel?.font = buttonFont
let buttonY = (font.lineHeight + self.lineSpacing) * CGFloat(self.maximumNumberOfLines - 1)
let size = self.moreButton.sizeThatFits(CGSize(width: 1000, height: 1000))
let moreButtonFrame = CGRect(x: self.bounds.width - self.moreButton.bounds.width,
y: buttonY,
width: size.width,
height: font.lineHeight)
self.moreButton.frame = moreButtonFrame
if self.isCollapsed
{
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
let maximumCollapsedHeight = font.lineHeight * CGFloat(self.maximumNumberOfLines)
if self.intrinsicContentSize.height > maximumCollapsedHeight
{
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.
self.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionFrame)]
self.moreButton.isHidden = false
}
else
{
self.textContainer.exclusionPaths = []
self.moreButton.isHidden = true
}
}
else
{
self.textContainer.maximumNumberOfLines = 0
self.textContainer.exclusionPaths = []
self.moreButton.isHidden = true
}
self.invalidateIntrinsicContentSize()
}
}
private extension CollapsingTextView
{
@objc func toggleCollapsed(_ sender: UIButton)
{
self.isCollapsed.toggle()
}
}
extension CollapsingTextView: NSLayoutManagerDelegate
{
func layoutManager(_ layoutManager: NSLayoutManager, lineSpacingAfterGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat
{
return self.lineSpacing
}
}

View File

@@ -1,192 +0,0 @@
//
// PillButton.swift
// AltStore
//
// Created by Riley Testut on 7/15/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
class PillButton: UIButton
{
override var accessibilityValue: String? {
get {
guard self.progress != nil else { return super.accessibilityValue }
return self.progressView.accessibilityValue
}
set { super.accessibilityValue = newValue }
}
var progress: Progress? {
didSet {
self.progressView.progress = Float(self.progress?.fractionCompleted ?? 0)
self.progressView.observedProgress = self.progress
let isUserInteractionEnabled = self.isUserInteractionEnabled
self.isIndicatingActivity = (self.progress != nil)
self.isUserInteractionEnabled = isUserInteractionEnabled
self.update()
}
}
var progressTintColor: UIColor? {
get {
return self.progressView.progressTintColor
}
set {
self.progressView.progressTintColor = newValue
}
}
var countdownDate: Date? {
didSet {
self.isEnabled = (self.countdownDate == nil)
self.displayLink.isPaused = (self.countdownDate == nil)
if self.countdownDate == nil
{
self.setTitle(nil, for: .disabled)
}
}
}
private let progressView = UIProgressView(progressViewStyle: .default)
private lazy var displayLink: CADisplayLink = {
let displayLink = CADisplayLink(target: self, selector: #selector(PillButton.updateCountdown))
displayLink.preferredFramesPerSecond = 15
displayLink.isPaused = true
displayLink.add(to: .main, forMode: .common)
return displayLink
}()
private let dateComponentsFormatter: DateComponentsFormatter = {
let dateComponentsFormatter = DateComponentsFormatter()
dateComponentsFormatter.zeroFormattingBehavior = [.pad]
dateComponentsFormatter.collapsesLargestUnit = false
return dateComponentsFormatter
}()
override var intrinsicContentSize: CGSize {
var size = super.intrinsicContentSize
size.width += 26
size.height += 3
return size
}
deinit
{
self.displayLink.remove(from: .main, forMode: RunLoop.Mode.default)
}
override func awakeFromNib()
{
super.awakeFromNib()
self.layer.masksToBounds = true
self.accessibilityTraits.formUnion([.updatesFrequently, .button])
self.activityIndicatorView.style = .white
self.activityIndicatorView.isUserInteractionEnabled = false
self.progressView.progress = 0
self.progressView.trackImage = UIImage()
self.progressView.isUserInteractionEnabled = false
self.addSubview(self.progressView)
self.update()
}
override func layoutSubviews()
{
super.layoutSubviews()
self.progressView.bounds.size.width = self.bounds.width
let scale = self.bounds.height / self.progressView.bounds.height
self.progressView.transform = CGAffineTransform.identity.scaledBy(x: 1, y: scale)
self.progressView.center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
self.layer.cornerRadius = self.bounds.midY
}
override func tintColorDidChange()
{
super.tintColorDidChange()
self.update()
}
}
private extension PillButton
{
func update()
{
if self.progress == nil
{
self.setTitleColor(.white, for: .normal)
self.backgroundColor = self.tintColor
}
else
{
self.setTitleColor(self.tintColor, for: .normal)
self.backgroundColor = self.tintColor.withAlphaComponent(0.15)
}
self.progressView.progressTintColor = self.tintColor
}
@objc func updateCountdown()
{
guard let endDate = self.countdownDate else { return }
let startDate = Date()
let interval = endDate.timeIntervalSince(startDate)
guard interval > 0 else {
self.isEnabled = true
return
}
let text: String?
if interval < (1 * 60 * 60)
{
self.dateComponentsFormatter.unitsStyle = .positional
self.dateComponentsFormatter.allowedUnits = [.minute, .second]
text = self.dateComponentsFormatter.string(from: startDate, to: endDate)
}
else if interval < (2 * 24 * 60 * 60)
{
self.dateComponentsFormatter.unitsStyle = .positional
self.dateComponentsFormatter.allowedUnits = [.hour, .minute, .second]
text = self.dateComponentsFormatter.string(from: startDate, to: endDate)
}
else
{
self.dateComponentsFormatter.unitsStyle = .full
self.dateComponentsFormatter.allowedUnits = [.day]
let numberOfDays = endDate.numberOfCalendarDays(since: startDate)
text = String(format: NSLocalizedString("%@ DAYS", comment: ""), NSNumber(value: numberOfDays))
}
if let text = text
{
UIView.performWithoutAnimation {
self.isEnabled = false
self.setTitle(text, for: .disabled)
self.layoutIfNeeded()
}
}
else
{
self.isEnabled = true
}
}
}

View File

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

View File

@@ -1,86 +0,0 @@
//
// AppManagerErrors.swift
// AltStore
//
// Created by Riley Testut on 8/27/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
import CoreData
import AltStoreCore
extension AppManager
{
struct FetchSourcesError: LocalizedError, CustomNSError
{
var primaryError: Error?
var sources: Set<Source>?
var errors = [Source: Error]()
var managedObjectContext: NSManagedObjectContext?
var errorDescription: String? {
if let error = self.primaryError
{
return error.localizedDescription
}
else
{
var localizedDescription: String?
self.managedObjectContext?.performAndWait {
if self.sources?.count == 1
{
localizedDescription = NSLocalizedString("Could not refresh store.", comment: "")
}
else if self.errors.count == 1
{
guard let source = self.errors.keys.first else { return }
localizedDescription = String(format: NSLocalizedString("Could not refresh source “%@”.", comment: ""), source.name)
}
else
{
localizedDescription = String(format: NSLocalizedString("Could not refresh %@ sources.", comment: ""), NSNumber(value: self.errors.count))
}
}
return localizedDescription
}
}
var recoverySuggestion: String? {
if let error = self.primaryError as NSError?
{
return error.localizedRecoverySuggestion
}
else if self.errors.count == 1
{
return nil
}
else
{
return NSLocalizedString("Tap to view source errors.", comment: "")
}
}
var errorUserInfo: [String : Any] {
guard let error = self.errors.values.first, self.errors.count == 1 else { return [:] }
return [NSUnderlyingErrorKey: error]
}
init(_ error: Error)
{
self.primaryError = error
}
init(sources: Set<Source>, errors: [Source: Error], context: NSManagedObjectContext)
{
self.sources = sources
self.errors = errors
self.managedObjectContext = context
}
}
}

View File

@@ -1,44 +0,0 @@
//
// InstalledAppsCollectionHeaderView.swift
// AltStore
//
// Created by Riley Testut on 3/9/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import UIKit
class InstalledAppsCollectionHeaderView: UICollectionReusableView
{
let textLabel: UILabel
let button: UIButton
override init(frame: CGRect)
{
self.textLabel = UILabel()
self.textLabel.translatesAutoresizingMaskIntoConstraints = false
self.textLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold)
self.textLabel.accessibilityTraits.insert(.header)
self.button = UIButton(type: .system)
self.button.translatesAutoresizingMaskIntoConstraints = false
self.button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
super.init(frame: frame)
self.addSubview(self.textLabel)
self.addSubview(self.button)
NSLayoutConstraint.activate([self.textLabel.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor),
self.textLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor)])
NSLayoutConstraint.activate([self.button.trailingAnchor.constraint(equalTo: self.layoutMarginsGuide.trailingAnchor),
self.button.firstBaselineAnchor.constraint(equalTo: self.textLabel.firstBaselineAnchor)])
self.preservesSuperviewLayoutMargins = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@@ -1,108 +0,0 @@
//
// UpdateCollectionViewCell.swift
// AltStore
//
// Created by Riley Testut on 7/16/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
extension UpdateCollectionViewCell
{
enum Mode
{
case collapsed
case expanded
}
}
@objc class UpdateCollectionViewCell: UICollectionViewCell
{
var mode: Mode = .expanded {
didSet {
self.update()
}
}
@IBOutlet var bannerView: AppBannerView!
@IBOutlet var versionDescriptionTitleLabel: UILabel!
@IBOutlet var versionDescriptionTextView: CollapsingTextView!
@IBOutlet private var blurView: UIVisualEffectView!
private var originalTintColor: UIColor?
override func awakeFromNib()
{
super.awakeFromNib()
// Prevent temporary unsatisfiable constraint errors due to UIView-Encapsulated-Layout constraints.
self.contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.contentView.preservesSuperviewLayoutMargins = true
self.bannerView.backgroundEffectView.isHidden = true
self.bannerView.button.setTitle(NSLocalizedString("UPDATE", comment: ""), for: .normal)
self.blurView.layer.cornerRadius = 20
self.blurView.layer.masksToBounds = true
self.update()
}
override func tintColorDidChange()
{
super.tintColorDidChange()
if self.tintAdjustmentMode != .dimmed
{
self.originalTintColor = self.tintColor
}
self.update()
}
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes)
{
// Animates transition to new attributes.
let animator = UIViewPropertyAnimator(springTimingParameters: UISpringTimingParameters()) {
self.layoutIfNeeded()
}
animator.startAnimation()
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
{
let view = super.hitTest(point, with: event)
if view == self.versionDescriptionTextView
{
// Forward touches on the text view (but not on the nested "more" button)
// so cell selection works as expected.
return self
}
else
{
return view
}
}
}
private extension UpdateCollectionViewCell
{
func update()
{
switch self.mode
{
case .collapsed: self.versionDescriptionTextView.isCollapsed = true
case .expanded: self.versionDescriptionTextView.isCollapsed = false
}
self.versionDescriptionTitleLabel.textColor = self.originalTintColor ?? self.tintColor
self.blurView.backgroundColor = self.originalTintColor ?? self.tintColor
self.bannerView.button.progressTintColor = self.originalTintColor ?? self.tintColor
self.setNeedsLayout()
self.layoutIfNeeded()
}
}

View File

@@ -1,30 +0,0 @@
//
// NewsCollectionViewCell.swift
// AltStore
//
// Created by Riley Testut on 8/29/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
class NewsCollectionViewCell: UICollectionViewCell
{
@IBOutlet var titleLabel: UILabel!
@IBOutlet var captionLabel: UILabel!
@IBOutlet var imageView: UIImageView!
@IBOutlet var contentBackgroundView: UIView!
override func awakeFromNib()
{
super.awakeFromNib()
self.contentView.preservesSuperviewLayoutMargins = true
self.contentBackgroundView.layer.cornerRadius = 30
self.contentBackgroundView.clipsToBounds = true
self.imageView.layer.cornerRadius = 30
self.imageView.clipsToBounds = true
}
}

View File

@@ -1,93 +0,0 @@
//
// Operation.swift
// AltStore
//
// Created by Riley Testut on 6/7/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
import Roxas
class ResultOperation<ResultType>: Operation
{
var resultHandler: ((Result<ResultType, Error>) -> Void)?
@available(*, unavailable)
override func finish()
{
super.finish()
}
func finish(_ result: Result<ResultType, Error>)
{
guard !self.isFinished else { return }
if self.isCancelled
{
self.resultHandler?(.failure(OperationError.cancelled))
}
else
{
self.resultHandler?(result)
}
super.finish()
}
}
class Operation: RSTOperation, ProgressReporting
{
let progress = Progress.discreteProgress(totalUnitCount: 1)
private var backgroundTaskID: UIBackgroundTaskIdentifier?
override var isAsynchronous: Bool {
return true
}
override init()
{
super.init()
self.progress.cancellationHandler = { [weak self] in self?.cancel() }
}
override func cancel()
{
super.cancel()
if !self.progress.isCancelled
{
self.progress.cancel()
}
}
override func main()
{
super.main()
let name = "com.altstore." + NSStringFromClass(type(of: self))
self.backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: name) { [weak self] in
guard let backgroundTask = self?.backgroundTaskID else { return }
self?.cancel()
UIApplication.shared.endBackgroundTask(backgroundTask)
self?.backgroundTaskID = .invalid
}
}
override func finish()
{
guard !self.isFinished else { return }
super.finish()
if let backgroundTaskID = self.backgroundTaskID
{
UIApplication.shared.endBackgroundTask(backgroundTaskID)
self.backgroundTaskID = .invalid
}
}
}

View File

@@ -1,499 +0,0 @@
//
// PatchViewController.swift
// AltStore
//
// Created by Riley Testut on 10/20/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
import UIKit
import Combine
import AltStoreCore
import AltSign
import Roxas
@available(iOS 14.0, *)
extension PatchViewController
{
enum Step
{
case confirm
case install
case openApp
case patchApp
case reboot
case refresh
case finish
}
}
@available(iOS 14.0, *)
class PatchViewController: UIViewController
{
var patchApp: AnyApp?
var installedApp: InstalledApp?
var completionHandler: ((Result<Void, Error>) -> Void)?
private let context = AuthenticatedOperationContext()
private var currentStep: Step = .confirm {
didSet {
DispatchQueue.main.async {
self.update()
}
}
}
private var buttonHandler: (() -> Void)?
private var resignedApp: ALTApplication?
private lazy var temporaryDirectory: URL = FileManager.default.uniqueTemporaryURL()
private var didEnterBackgroundObservation: NSObjectProtocol?
private weak var cancellableProgress: Progress?
@IBOutlet private var placeholderView: RSTPlaceholderView!
@IBOutlet private var taskDescriptionLabel: UILabel!
@IBOutlet private var pillButton: PillButton!
@IBOutlet private var cancelBarButtonItem: UIBarButtonItem!
@IBOutlet private var cancelButton: UIButton!
override func viewDidLoad()
{
super.viewDidLoad()
self.isModalInPresentation = true
self.placeholderView.stackView.spacing = 20
self.placeholderView.textLabel.textColor = .white
self.placeholderView.detailTextLabel.textAlignment = .left
self.placeholderView.detailTextLabel.textColor = UIColor.white.withAlphaComponent(0.6)
self.buttonHandler = { [weak self] in
self?.startProcess()
}
do
{
try FileManager.default.createDirectory(at: self.temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
}
catch
{
print("Failed to create temporary directory:", error)
}
self.update()
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
if self.installedApp != nil
{
self.refreshApp()
}
}
}
@available(iOS 14.0, *)
private extension PatchViewController
{
func update()
{
self.cancelButton.alpha = 0.0
switch self.currentStep
{
case .confirm:
guard let app = self.patchApp else { break }
if UIDevice.current.isUntetheredJailbreakRequired
{
self.placeholderView.textLabel.text = NSLocalizedString("Jailbreak Requires Untethering", comment: "")
self.placeholderView.detailTextLabel.text = String(format: NSLocalizedString("This jailbreak is untethered, which means %@ will never expire — even after 7 days or rebooting the device.\n\nInstalling an untethered jailbreak requires a few extra steps, but SideStore will walk you through the process.\n\nWould you like to continue? ", comment: ""), app.name)
}
else
{
self.placeholderView.textLabel.text = NSLocalizedString("Jailbreak Supports Untethering", comment: "")
self.placeholderView.detailTextLabel.text = String(format: NSLocalizedString("This jailbreak has an untethered version, which means %@ will never expire — even after 7 days or rebooting the device.\n\nInstalling an untethered jailbreak requires a few extra steps, but SideStore will walk you through the process.\n\nWould you like to continue? ", comment: ""), app.name)
}
self.pillButton.setTitle(NSLocalizedString("Install Untethered Jailbreak", comment: ""), for: .normal)
self.cancelButton.alpha = 1.0
case .install:
guard let app = self.patchApp else { break }
self.placeholderView.textLabel.text = String(format: NSLocalizedString("Installing %@ placeholder…", comment: ""), app.name)
self.placeholderView.detailTextLabel.text = NSLocalizedString("A placeholder app needs to be installed in order to prepare your device for untethering.\n\nThis may take a few moments.", comment: "")
case .openApp:
self.placeholderView.textLabel.text = NSLocalizedString("Continue in App", comment: "")
self.placeholderView.detailTextLabel.text = NSLocalizedString("Please open the placeholder app and follow the instructions to continue jailbreaking your device.", comment: "")
self.pillButton.setTitle(NSLocalizedString("Open Placeholder", comment: ""), for: .normal)
case .patchApp:
guard let app = self.patchApp else { break }
self.placeholderView.textLabel.text = String(format: NSLocalizedString("Patching %@ placeholder…", comment: ""), app.name)
self.placeholderView.detailTextLabel.text = NSLocalizedString("This will take a few moments. Please do not turn off the screen or leave the app until patching is complete.", comment: "")
self.pillButton.setTitle(NSLocalizedString("Patch Placeholder", comment: ""), for: .normal)
case .reboot:
self.placeholderView.textLabel.text = NSLocalizedString("Continue in App", comment: "")
self.placeholderView.detailTextLabel.text = NSLocalizedString("Please open the placeholder app and follow the instructions to continue jailbreaking your device.", comment: "")
self.pillButton.setTitle(NSLocalizedString("Open Placeholder", comment: ""), for: .normal)
case .refresh:
guard let installedApp = self.installedApp else { break }
self.placeholderView.textLabel.text = String(format: NSLocalizedString("Finish installing %@?", comment: ""), installedApp.name)
self.placeholderView.detailTextLabel.text = String(format: NSLocalizedString("In order to finish jailbreaking this device, you need to install %@ then follow the instructions in the app.", comment: ""), installedApp.name)
self.pillButton.setTitle(String(format: NSLocalizedString("Install %@", comment: ""), installedApp.name), for: .normal)
case .finish:
guard let installedApp = self.installedApp else { break }
self.placeholderView.textLabel.text = String(format: NSLocalizedString("Finish in %@", comment: ""), installedApp.name)
self.placeholderView.detailTextLabel.text = String(format: NSLocalizedString("Follow the instructions in %@ to finish jailbreaking this device.", comment: ""), installedApp.name)
self.pillButton.setTitle(String(format: NSLocalizedString("Open %@", comment: ""), installedApp.name), for: .normal)
}
}
func present(_ error: Error, title: String)
{
DispatchQueue.main.async {
let nsError = error as NSError
let alertController = UIAlertController(title: nsError.localizedFailure ?? title, message: error.localizedDescription, preferredStyle: .alert)
alertController.addAction(.ok)
self.present(alertController, animated: true, completion: nil)
self.setProgress(nil, description: nil)
}
}
func setProgress(_ progress: Progress?, description: String?)
{
DispatchQueue.main.async {
self.pillButton.progress = progress
self.taskDescriptionLabel.text = description ?? " " // Use non-empty string to prevent label resizing itself.
}
}
func finish(with result: Result<Void, Error>)
{
do
{
try FileManager.default.removeItem(at: self.temporaryDirectory)
}
catch
{
print("Failed to remove temporary directory:", error)
}
if let observation = self.didEnterBackgroundObservation
{
NotificationCenter.default.removeObserver(observation)
}
self.completionHandler?(result)
self.completionHandler = nil
}
}
@available(iOS 14.0, *)
private extension PatchViewController
{
@IBAction func performButtonAction()
{
self.buttonHandler?()
}
@IBAction func cancel()
{
self.finish(with: .success(()))
self.cancellableProgress?.cancel()
}
@IBAction func installRegularJailbreak()
{
guard let app = self.patchApp else { return }
let title: String
let message: String
if UIDevice.current.isUntetheredJailbreakRequired
{
title = NSLocalizedString("Untethering Required", comment: "")
message = String(format: NSLocalizedString("%@ can not jailbreak this device unless you untether it first. Are you sure you want to install without untethering?", comment: ""), app.name)
}
else
{
title = NSLocalizedString("Untethering Recommended", comment: "")
message = String(format: NSLocalizedString("Untethering this jailbreak will prevent %@ from expiring, even after 7 days or rebooting the device. Are you sure you want to install without untethering?", comment: ""), app.name)
}
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Install Without Untethering", comment: ""), style: .default) { _ in
self.finish(with: .failure(OperationError.cancelled))
})
alertController.addAction(.cancel)
self.present(alertController, animated: true, completion: nil)
}
}
@available(iOS 14.0, *)
private extension PatchViewController
{
func startProcess()
{
guard let patchApp = self.patchApp else { return }
self.currentStep = .install
if let progress = AppManager.shared.installationProgress(for: patchApp)
{
// Cancel pending jailbreak app installation so we can start a new one.
progress.cancel()
}
let appURL = InstalledApp.fileURL(for: patchApp)
let cachedAppURL = self.temporaryDirectory.appendingPathComponent("Cached.app")
do
{
// Make copy of original app, so we can replace the cached patch app with it later.
try FileManager.default.copyItem(at: appURL, to: cachedAppURL, shouldReplace: true)
}
catch
{
self.present(error, title: NSLocalizedString("Could not back up jailbreak app.", comment: ""))
return
}
var unzippingError: Error?
let refreshGroup = AppManager.shared.install(patchApp, presentingViewController: self, context: self.context) { result in
do
{
_ = try result.get()
if let unzippingError = unzippingError
{
throw unzippingError
}
// Replace cached patch app with original app so we can resume installing it post-reboot.
try FileManager.default.copyItem(at: cachedAppURL, to: appURL, shouldReplace: true)
self.openApp()
}
catch
{
self.present(error, title: String(format: NSLocalizedString("Could not install %@ placeholder.", comment: ""), patchApp.name))
}
}
refreshGroup.beginInstallationHandler = { (installedApp) in
do
{
// Replace patch app name with correct name.
installedApp.name = patchApp.name
let ipaURL = installedApp.refreshedIPAURL
let resignedAppURL = try FileManager.default.unzipAppBundle(at: ipaURL, toDirectory: self.temporaryDirectory)
self.resignedApp = ALTApplication(fileURL: resignedAppURL)
}
catch
{
print("Error unzipping app bundle:", error)
unzippingError = error
}
}
self.setProgress(refreshGroup.progress, description: nil)
self.cancellableProgress = refreshGroup.progress
}
func openApp()
{
guard let patchApp = self.patchApp else { return }
self.setProgress(nil, description: nil)
self.currentStep = .openApp
// This observation is willEnterForeground because patching starts immediately upon return.
self.didEnterBackgroundObservation = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { (notification) in
self.didEnterBackgroundObservation.map { NotificationCenter.default.removeObserver($0) }
self.patchApplication()
}
self.buttonHandler = { [weak self] in
guard let self = self else { return }
#if !targetEnvironment(simulator)
let openURL = InstalledApp.openAppURL(for: patchApp)
UIApplication.shared.open(openURL) { success in
guard !success else { return }
self.present(OperationError.openAppFailed(name: patchApp.name), title: String(format: NSLocalizedString("Could not open %@ placeholder.", comment: ""), patchApp.name))
}
#endif
}
}
func patchApplication()
{
guard let resignedApp = self.resignedApp else { return }
self.currentStep = .patchApp
self.buttonHandler = { [weak self] in
self?.patchApplication()
}
let patchAppOperation = AppManager.shared.patch(resignedApp: resignedApp, presentingViewController: self, context: self.context) { result in
switch result
{
case .failure(let error): self.present(error, title: String(format: NSLocalizedString("Could not patch %@ placeholder.", comment: ""), resignedApp.name))
case .success: self.rebootDevice()
}
}
patchAppOperation.progressHandler = { (progress, description) in
self.setProgress(progress, description: description)
}
self.cancellableProgress = patchAppOperation.progress
}
func rebootDevice()
{
guard let patchApp = self.patchApp else { return }
self.setProgress(nil, description: nil)
self.currentStep = .reboot
self.didEnterBackgroundObservation = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { (notification) in
self.didEnterBackgroundObservation.map { NotificationCenter.default.removeObserver($0) }
var patchedApps = UserDefaults.standard.patchedApps ?? []
if !patchedApps.contains(patchApp.bundleIdentifier)
{
patchedApps.append(patchApp.bundleIdentifier)
UserDefaults.standard.patchedApps = patchedApps
}
self.finish(with: .success(()))
}
self.buttonHandler = { [weak self] in
guard let self = self else { return }
#if !targetEnvironment(simulator)
let openURL = InstalledApp.openAppURL(for: patchApp)
UIApplication.shared.open(openURL) { success in
guard !success else { return }
self.present(OperationError.openAppFailed(name: patchApp.name), title: String(format: NSLocalizedString("Could not open %@ placeholder.", comment: ""), patchApp.name))
}
#endif
}
}
func refreshApp()
{
guard let installedApp = self.installedApp else { return }
self.currentStep = .refresh
self.buttonHandler = { [weak self] in
guard let self = self else { return }
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
let tempApp = context.object(with: installedApp.objectID) as! InstalledApp
tempApp.needsResign = true
let errorTitle = String(format: NSLocalizedString("Could not install %@.", comment: ""), tempApp.name)
do
{
try context.save()
installedApp.managedObjectContext?.perform {
// Refreshing ensures we don't attempt to patch the app again,
// since that is only checked when installing a new app.
let refreshGroup = AppManager.shared.refresh([installedApp], presentingViewController: self, group: nil)
refreshGroup.completionHandler = { [weak refreshGroup, weak self] (results) in
guard let self = self else { return }
do
{
guard let (bundleIdentifier, result) = results.first else { throw refreshGroup?.context.error ?? OperationError.unknown }
_ = try result.get()
if var patchedApps = UserDefaults.standard.patchedApps, let index = patchedApps.firstIndex(of: bundleIdentifier)
{
patchedApps.remove(at: index)
UserDefaults.standard.patchedApps = patchedApps
}
self.finish()
}
catch
{
self.present(error, title: errorTitle)
}
}
self.setProgress(refreshGroup.progress, description: String(format: NSLocalizedString("Installing %@...", comment: ""), installedApp.name))
}
}
catch
{
self.present(error, title: errorTitle)
}
}
}
}
func finish()
{
guard let installedApp = self.installedApp else { return }
self.setProgress(nil, description: nil)
self.currentStep = .finish
self.didEnterBackgroundObservation = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { (notification) in
self.didEnterBackgroundObservation.map { NotificationCenter.default.removeObserver($0) }
self.finish(with: .success(()))
}
installedApp.managedObjectContext?.perform {
let appName = installedApp.name
let openURL = installedApp.openAppURL
self.buttonHandler = { [weak self] in
guard let self = self else { return }
#if !targetEnvironment(simulator)
UIApplication.shared.open(openURL) { success in
guard !success else { return }
self.present(OperationError.openAppFailed(name: appName), title: String(format: NSLocalizedString("Could not open %@.", comment: ""), appName))
}
#endif
}
}
}
}

View File

@@ -1,18 +0,0 @@
//
// fragmentzip.h
// AltStore
//
// Created by Riley Testut on 10/25/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
#ifndef fragmentzip_h
#define fragmentzip_h
typedef void fragmentzip_t;
typedef void (*fragmentzip_process_callback_t)(unsigned int progress);
fragmentzip_t *fragmentzip_open(const char *url);
int fragmentzip_download_file(fragmentzip_t *info, const char *remotepath, const char *savepath, fragmentzip_process_callback_t callback);
void fragmentzip_close(fragmentzip_t *info);
#endif /* fragmentzip_h */

View File

@@ -1,77 +0,0 @@
//
// RemoveAppBackupOperation.swift
// AltStore
//
// Created by Riley Testut on 5/13/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
@objc(RemoveAppBackupOperation)
class RemoveAppBackupOperation: ResultOperation<Void>
{
let context: InstallAppOperationContext
private let coordinator = NSFileCoordinator()
private let coordinatorQueue = OperationQueue()
init(context: InstallAppOperationContext)
{
self.context = context
super.init()
self.coordinatorQueue.name = "AltStore - RemoveAppBackupOperation Queue"
}
override func main()
{
super.main()
if let error = self.context.error
{
self.finish(.failure(error))
return
}
guard let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
installedApp.managedObjectContext?.perform {
guard let backupDirectoryURL = FileManager.default.backupDirectoryURL(for: installedApp) else { return self.finish(.failure(OperationError.missingAppGroup)) }
let intent = NSFileAccessIntent.writingIntent(with: backupDirectoryURL, options: [.forDeleting])
self.coordinator.coordinate(with: [intent], queue: self.coordinatorQueue) { (error) in
do
{
if let error = error
{
throw error
}
try FileManager.default.removeItem(at: intent.url)
self.finish(.success(()))
}
catch let error as CocoaError where error.code == CocoaError.Code.fileNoSuchFile
{
#if DEBUG
// When debugging, it's expected that app groups don't match, so ignore.
self.finish(.success(()))
#else
print("Failed to remove app backup directory:", error)
self.finish(.failure(error))
#endif
}
catch
{
print("Failed to remove app backup directory:", error)
self.finish(.failure(error))
}
}
}
}
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -1,22 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "sound@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "sound@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,22 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "fetch@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "fetch@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,22 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "photos@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "photos@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

View File

@@ -1,29 +0,0 @@
<?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>StringsTable</key>
<string>Root</string>
<key>ApplicationGroupContainerIdentifier</key>
<string>group.$(APP_GROUP_IDENTIFIER)</string>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>Type</key>
<string>PSTextFieldSpecifier</string>
<key>Title</key>
<string>Anisette URL</string>
<key>Key</key>
<string>customAnisetteURL</string>
<key>IsSecure</key>
<string>Alphabet</string>
<key>AutocapitalizationType</key>
<string>None</string>
<key>AutocorrectionType</key>
<string>No</string>
<key>KeyboardType</key>
<string>URL</string>
</dict>
</array>
</dict>
</plist>

View File

@@ -1,132 +0,0 @@
//
// InsetGroupTableViewCell.swift
// AltStore
//
// Created by Riley Testut on 8/31/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
extension InsetGroupTableViewCell
{
@objc enum Style: Int
{
case single
case top
case middle
case bottom
}
}
class InsetGroupTableViewCell: UITableViewCell
{
#if !TARGET_INTERFACE_BUILDER
@IBInspectable var style: Style = .single {
didSet {
self.update()
}
}
#else
@IBInspectable var style: Int = 0
#endif
@IBInspectable var isSelectable: Bool = false
private let separatorView = UIView()
private let insetView = UIView()
override func awakeFromNib()
{
super.awakeFromNib()
self.selectionStyle = .none
self.separatorView.translatesAutoresizingMaskIntoConstraints = false
self.separatorView.backgroundColor = UIColor.white.withAlphaComponent(0.25)
self.addSubview(self.separatorView)
self.insetView.layer.masksToBounds = true
self.insetView.layer.cornerRadius = 16
// Get the preferred background color from Interface Builder.
self.insetView.backgroundColor = self.backgroundColor
self.backgroundColor = nil
self.addSubview(self.insetView, pinningEdgesWith: UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 15))
self.sendSubviewToBack(self.insetView)
NSLayoutConstraint.activate([self.separatorView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 30),
self.separatorView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -30),
self.separatorView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
self.separatorView.heightAnchor.constraint(equalToConstant: 1)])
self.update()
}
override func setSelected(_ selected: Bool, animated: Bool)
{
super.setSelected(selected, animated: animated)
if animated
{
UIView.animate(withDuration: 0.4) {
self.update()
}
}
else
{
self.update()
}
}
override func setHighlighted(_ highlighted: Bool, animated: Bool)
{
super.setHighlighted(highlighted, animated: animated)
if animated
{
UIView.animate(withDuration: 0.4) {
self.update()
}
}
else
{
self.update()
}
}
}
private extension InsetGroupTableViewCell
{
func update()
{
switch self.style
{
case .single:
self.insetView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
self.separatorView.isHidden = true
case .top:
self.insetView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
self.separatorView.isHidden = false
case .middle:
self.insetView.layer.maskedCorners = []
self.separatorView.isHidden = false
case .bottom:
self.insetView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
self.separatorView.isHidden = true
}
if self.isSelectable && (self.isHighlighted || self.isSelected)
{
self.insetView.backgroundColor = UIColor.white.withAlphaComponent(0.55)
}
else
{
self.insetView.backgroundColor = UIColor.white.withAlphaComponent(0.25)
}
}
}

View File

@@ -1,53 +0,0 @@
//
// LicensesViewController.swift
// AltStore
//
// Created by Riley Testut on 9/6/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
class LicensesViewController: UIViewController
{
private var _didAppear = false
@IBOutlet private var textView: UITextView!
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
// Fix incorrect initial offset on iPhone SE.
self.textView.contentOffset.y = 0
}
override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)
_didAppear = true
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
self.textView.textContainerInset.left = self.view.layoutMargins.left
self.textView.textContainerInset.right = self.view.layoutMargins.right
self.textView.textContainer.lineFragmentPadding = 0
if !_didAppear
{
// Fix incorrect initial offset on iPhone SE.
self.textView.contentOffset.y = 0
}
}
}

View File

@@ -1,96 +0,0 @@
//
// PatreonComponents.swift
// AltStore
//
// Created by Riley Testut on 9/5/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
class PatronCollectionViewCell: UICollectionViewCell
{
@IBOutlet var textLabel: UILabel!
}
class PatronsHeaderView: UICollectionReusableView
{
let textLabel = UILabel()
override init(frame: CGRect)
{
super.init(frame: frame)
self.textLabel.font = UIFont.boldSystemFont(ofSize: 17)
self.textLabel.textColor = .white
self.addSubview(self.textLabel, pinningEdgesWith: UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class PatronsFooterView: UICollectionReusableView
{
let button = UIButton(type: .system)
override init(frame: CGRect)
{
super.init(frame: frame)
self.button.translatesAutoresizingMaskIntoConstraints = false
self.button.activityIndicatorView.style = .white
self.button.titleLabel?.textColor = .white
self.addSubview(self.button)
NSLayoutConstraint.activate([self.button.centerXAnchor.constraint(equalTo: self.centerXAnchor),
self.button.centerYAnchor.constraint(equalTo: self.centerYAnchor)])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class AboutPatreonHeaderView: UICollectionReusableView
{
@IBOutlet var supportButton: UIButton!
@IBOutlet var accountButton: UIButton!
@IBOutlet var textView: UITextView!
@IBOutlet private var rileyLabel: UILabel!
@IBOutlet private var shaneLabel: UILabel!
@IBOutlet private var rileyImageView: UIImageView!
@IBOutlet private var shaneImageView: UIImageView!
override func awakeFromNib()
{
super.awakeFromNib()
self.textView.clipsToBounds = true
self.textView.layer.cornerRadius = 20
self.textView.textContainer.lineFragmentPadding = 0
for imageView in [self.rileyImageView!, self.shaneImageView!]
{
imageView.clipsToBounds = true
imageView.layer.cornerRadius = imageView.bounds.midY
}
for button in [self.supportButton!, self.accountButton!]
{
button.clipsToBounds = true
button.layer.cornerRadius = 16
}
}
override func layoutMarginsDidChange()
{
super.layoutMarginsDidChange()
self.textView.textContainerInset = UIEdgeInsets(top: self.layoutMargins.left, left: self.layoutMargins.left, bottom: self.layoutMargins.right, right: self.layoutMargins.right)
}
}

View File

@@ -1,36 +0,0 @@
//
// SettingsHeaderFooterView.swift
// AltStore
//
// Created by Riley Testut on 8/31/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
import Roxas
class SettingsHeaderFooterView: UITableViewHeaderFooterView
{
@IBOutlet var primaryLabel: UILabel!
@IBOutlet var secondaryLabel: UILabel!
@IBOutlet var button: UIButton!
@IBOutlet private var stackView: UIStackView!
override func awakeFromNib()
{
super.awakeFromNib()
self.contentView.layoutMargins = .zero
self.contentView.preservesSuperviewLayoutMargins = true
self.stackView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(self.stackView)
NSLayoutConstraint.activate([self.stackView.leadingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.leadingAnchor),
self.stackView.trailingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.trailingAnchor),
self.stackView.topAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.topAnchor),
self.stackView.bottomAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.bottomAnchor)])
}
}

View File

@@ -1,3 +0,0 @@
#include "Build.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER).AltStoreCore

View File

@@ -1,27 +0,0 @@
//
// AltStoreCore.h
// AltStoreCore
//
// Created by Riley Testut on 9/3/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
//! Project version number for AltStoreCore.
FOUNDATION_EXPORT double AltStoreCoreVersionNumber;
//! Project version string for AltStoreCore.
FOUNDATION_EXPORT const unsigned char AltStoreCoreVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <AltStoreCore/PublicHeader.h>
#import <AltStoreCore/ALTAppPermission.h>
#import <AltStoreCore/ALTSourceUserInfoKey.h>
#import <AltStoreCore/ALTPatreonBenefitType.h>
// Shared
#import <AltStoreCore/ALTConstants.h>
#import <AltStoreCore/ALTConnection.h>
#import <AltStoreCore/NSError+ALTServerError.h>
#import <AltStoreCore/CFNotificationName+AltStore.h>

View File

@@ -1,30 +0,0 @@
//
// FileManager+SharedDirectories.swift
// AltStore
//
// Created by Riley Testut on 5/14/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
public extension FileManager
{
var altstoreSharedDirectory: URL? {
guard let appGroup = Bundle.main.appGroups.first else { return nil }
let sharedDirectoryURL = self.containerURL(forSecurityApplicationGroupIdentifier: appGroup)
return sharedDirectoryURL
}
var appBackupsDirectory: URL? {
let appBackupsDirectory = self.altstoreSharedDirectory?.appendingPathComponent("Backups", isDirectory: true)
return appBackupsDirectory
}
func backupDirectoryURL(for app: InstalledApp) -> URL?
{
let backupDirectoryURL = self.appBackupsDirectory?.appendingPathComponent(app.bundleIdentifier, isDirectory: true)
return backupDirectoryURL
}
}

View File

@@ -1,43 +0,0 @@
//
// UIColor+Hex.swift
// AltStore
//
// Created by Riley Testut on 7/15/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
public extension UIColor
{
// Borrowed from https://stackoverflow.com/a/26341062
var hexString: String {
let components = self.cgColor.components
let r: CGFloat = components?[0] ?? 0.0
let g: CGFloat = components?[1] ?? 0.0
let b: CGFloat = components?[2] ?? 0.0
let hexString = String.init(format: "%02lX%02lX%02lX", lroundf(Float(r * 255)), lroundf(Float(g * 255)), lroundf(Float(b * 255)))
return hexString
}
// Borrowed from https://stackoverflow.com/a/33397427
convenience init?(hexString: String)
{
let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int = UInt32()
Scanner(string: hex).scanHexInt32(&int)
let a, r, g, b: UInt32
switch hex.count {
case 3: // RGB (12-bit)
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // ARGB (32-bit)
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
return nil
}
self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
}
}

View File

@@ -1,98 +0,0 @@
//
// AppPermission.swift
// AltStore
//
// Created by Riley Testut on 7/23/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import CoreData
import UIKit
public extension ALTAppPermissionType
{
var localizedShortName: String? {
switch self
{
case .photos: return NSLocalizedString("Photos", comment: "")
case .backgroundAudio: return NSLocalizedString("Audio (BG)", comment: "")
case .backgroundFetch: return NSLocalizedString("Fetch (BG)", comment: "")
default: return nil
}
}
var localizedName: String? {
switch self
{
case .photos: return NSLocalizedString("Photos", comment: "")
case .backgroundAudio: return NSLocalizedString("Background Audio", comment: "")
case .backgroundFetch: return NSLocalizedString("Background Fetch", comment: "")
default: return nil
}
}
var icon: UIImage? {
switch self
{
case .photos: return UIImage(named: "PhotosPermission")
case .backgroundAudio: return UIImage(named: "BackgroundAudioPermission")
case .backgroundFetch: return UIImage(named: "BackgroundFetchPermission")
default: return nil
}
}
}
@objc(AppPermission)
public class AppPermission: NSManagedObject, Decodable, Fetchable
{
/* Properties */
@NSManaged public var type: ALTAppPermissionType
@NSManaged public var usageDescription: String
/* Relationships */
@NSManaged public private(set) var app: StoreApp!
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
{
super.init(entity: entity, insertInto: context)
}
private enum CodingKeys: String, CodingKey
{
case type
case usageDescription
}
public required init(from decoder: Decoder) throws
{
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
super.init(entity: AppPermission.entity(), insertInto: context)
do
{
let container = try decoder.container(keyedBy: CodingKeys.self)
self.usageDescription = try container.decode(String.self, forKey: .usageDescription)
let rawType = try container.decode(String.self, forKey: .type)
self.type = ALTAppPermissionType(rawValue: rawType)
}
catch
{
if let context = self.managedObjectContext
{
context.delete(self)
}
throw error
}
}
}
public extension AppPermission
{
@nonobjc class func fetchRequest() -> NSFetchRequest<AppPermission>
{
return NSFetchRequest<AppPermission>(entityName: "AppPermission")
}
}

View File

@@ -1,75 +0,0 @@
//
// InstalledExtension.swift
// AltStore
//
// Created by Riley Testut on 1/7/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
import CoreData
import AltSign
@objc(InstalledExtension)
public class InstalledExtension: NSManagedObject, InstalledAppProtocol
{
/* Properties */
@NSManaged public var name: String
@NSManaged public var bundleIdentifier: String
@NSManaged public var resignedBundleIdentifier: String
@NSManaged public var version: String
@NSManaged public var refreshedDate: Date
@NSManaged public var expirationDate: Date
@NSManaged public var installedDate: Date
/* Relationships */
@NSManaged public var parentApp: InstalledApp?
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
{
super.init(entity: entity, insertInto: context)
}
public init(resignedAppExtension: ALTApplication, originalBundleIdentifier: String, context: NSManagedObjectContext)
{
super.init(entity: InstalledExtension.entity(), insertInto: context)
self.bundleIdentifier = originalBundleIdentifier
self.refreshedDate = Date()
self.installedDate = Date()
self.expirationDate = self.refreshedDate.addingTimeInterval(60 * 60 * 24 * 7) // Rough estimate until we get real values from provisioning profile.
self.update(resignedAppExtension: resignedAppExtension)
}
public func update(resignedAppExtension: ALTApplication)
{
self.name = resignedAppExtension.name
self.resignedBundleIdentifier = resignedAppExtension.bundleIdentifier
self.version = resignedAppExtension.version
if let provisioningProfile = resignedAppExtension.provisioningProfile
{
self.update(provisioningProfile: provisioningProfile)
}
}
public func update(provisioningProfile: ALTProvisioningProfile)
{
self.refreshedDate = provisioningProfile.creationDate
self.expirationDate = provisioningProfile.expirationDate
}
}
public extension InstalledExtension
{
@nonobjc class func fetchRequest() -> NSFetchRequest<InstalledExtension>
{
return NSFetchRequest<InstalledExtension>(entityName: "InstalledExtension")
}
}

View File

@@ -1,88 +0,0 @@
//
// MergePolicy.swift
// AltStore
//
// Created by Riley Testut on 7/23/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import CoreData
import Roxas
open class MergePolicy: RSTRelationshipPreservingMergePolicy
{
open override func resolve(constraintConflicts conflicts: [NSConstraintConflict]) throws
{
guard conflicts.allSatisfy({ $0.databaseObject != nil }) else {
for conflict in conflicts
{
switch conflict.conflictingObjects.first
{
case is StoreApp where conflict.conflictingObjects.count == 2:
// Modified cached StoreApp while replacing it with new one, causing context-level conflict.
// Most likely, we set up a relationship between the new StoreApp and a NewsItem,
// causing cached StoreApp to delete it's NewsItem relationship, resulting in (resolvable) conflict.
if let previousApp = conflict.conflictingObjects.first(where: { !$0.isInserted }) as? StoreApp
{
// Delete previous permissions (same as below).
for permission in previousApp.permissions
{
permission.managedObjectContext?.delete(permission)
}
}
default:
// Unknown context-level conflict.
assertionFailure("MergePolicy is only intended to work with database-level conflicts.")
}
}
try super.resolve(constraintConflicts: conflicts)
return
}
for conflict in conflicts
{
switch conflict.databaseObject
{
case let databaseObject as StoreApp:
// Delete previous permissions
for permission in databaseObject.permissions
{
permission.managedObjectContext?.delete(permission)
}
case let databaseObject as Source:
guard let conflictedObject = conflict.conflictingObjects.first as? Source else { break }
let bundleIdentifiers = Set(conflictedObject.apps.map { $0.bundleIdentifier })
let newsItemIdentifiers = Set(conflictedObject.newsItems.map { $0.identifier })
for app in databaseObject.apps
{
if !bundleIdentifiers.contains(app.bundleIdentifier)
{
// No longer listed in Source, so remove it from database.
app.managedObjectContext?.delete(app)
}
}
for newsItem in databaseObject.newsItems
{
if !newsItemIdentifiers.contains(newsItem.identifier)
{
// No longer listed in Source, so remove it from database.
newsItem.managedObjectContext?.delete(newsItem)
}
}
default: break
}
}
try super.resolve(constraintConflicts: conflicts)
}
}

View File

@@ -1,74 +0,0 @@
//
// PatreonAccount.swift
// AltStore
//
// Created by Riley Testut on 8/20/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import CoreData
extension PatreonAPI
{
struct AccountResponse: Decodable
{
struct Data: Decodable
{
struct Attributes: Decodable
{
var first_name: String?
var full_name: String
}
var id: String
var attributes: Attributes
}
var data: Data
var included: [PatronResponse]?
}
}
@objc(PatreonAccount)
public class PatreonAccount: NSManagedObject, Fetchable
{
@NSManaged public var identifier: String
@NSManaged public var name: String
@NSManaged public var firstName: String?
@NSManaged public var isPatron: Bool
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
{
super.init(entity: entity, insertInto: context)
}
init(response: PatreonAPI.AccountResponse, context: NSManagedObjectContext)
{
super.init(entity: PatreonAccount.entity(), insertInto: context)
self.identifier = response.data.id
self.name = response.data.attributes.full_name
self.firstName = response.data.attributes.first_name
if let patronResponse = response.included?.first
{
let patron = Patron(response: patronResponse)
self.isPatron = (patron.status == .active)
}
else
{
self.isPatron = false
}
}
}
public extension PatreonAccount
{
@nonobjc class func fetchRequest() -> NSFetchRequest<PatreonAccount>
{
return NSFetchRequest<PatreonAccount>(entityName: "PatreonAccount")
}
}

View File

@@ -1,172 +0,0 @@
//
// Source.swift
// AltStore
//
// Created by Riley Testut on 7/30/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import CoreData
public extension Source
{
#if ALPHA
static let altStoreIdentifier = "com.SideStore.SideStore"
#else
static let altStoreIdentifier = "com.SideStore.SideStore"
#endif
#if STAGING
#if ALPHA
static let altStoreSourceURL = URL(string: "https://apps.sidestore.io/")!
#else
static let altStoreSourceURL = URL(string: "https://apps.sidestore.io/")!
#endif
#else
#if ALPHA
static let altStoreSourceURL = URL(string: "https://apps.sidestore.io/")!
#else
static let altStoreSourceURL = URL(string: "https://apps.sidestore.io/")!
#endif
#endif
}
@objc(Source)
public class Source: NSManagedObject, Fetchable, Decodable
{
/* Properties */
@NSManaged public var name: String
@NSManaged public var identifier: String
@NSManaged public var sourceURL: URL
@NSManaged public var error: NSError?
/* Non-Core Data Properties */
public var userInfo: [ALTSourceUserInfoKey: String]?
/* Relationships */
@objc(apps) @NSManaged public private(set) var _apps: NSOrderedSet
@objc(newsItems) @NSManaged public private(set) var _newsItems: NSOrderedSet
@nonobjc public var apps: [StoreApp] {
get {
return self._apps.array as! [StoreApp]
}
set {
self._apps = NSOrderedSet(array: newValue)
}
}
@nonobjc public var newsItems: [NewsItem] {
get {
return self._newsItems.array as! [NewsItem]
}
set {
self._newsItems = NSOrderedSet(array: newValue)
}
}
private enum CodingKeys: String, CodingKey
{
case name
case identifier
case sourceURL
case userInfo
case apps
case news
}
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
{
super.init(entity: entity, insertInto: context)
}
public required init(from decoder: Decoder) throws
{
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
guard let sourceURL = decoder.sourceURL else { preconditionFailure("Decoder must have non-nil sourceURL.") }
super.init(entity: Source.entity(), insertInto: context)
do
{
self.sourceURL = sourceURL
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.identifier = try container.decode(String.self, forKey: .identifier)
let userInfo = try container.decodeIfPresent([String: String].self, forKey: .userInfo)
self.userInfo = userInfo?.reduce(into: [:]) { $0[ALTSourceUserInfoKey($1.key)] = $1.value }
let apps = try container.decodeIfPresent([StoreApp].self, forKey: .apps) ?? []
let appsByID = Dictionary(apps.map { ($0.bundleIdentifier, $0) }, uniquingKeysWith: { (a, b) in return a })
for (index, app) in apps.enumerated()
{
app.sourceIdentifier = self.identifier
app.sortIndex = Int32(index)
}
self._apps = NSMutableOrderedSet(array: apps)
let newsItems = try container.decodeIfPresent([NewsItem].self, forKey: .news) ?? []
for (index, item) in newsItems.enumerated()
{
item.sourceIdentifier = self.identifier
item.sortIndex = Int32(index)
}
for newsItem in newsItems
{
guard let appID = newsItem.appID else { continue }
if let storeApp = appsByID[appID]
{
newsItem.storeApp = storeApp
}
else
{
newsItem.storeApp = nil
}
}
self._newsItems = NSMutableOrderedSet(array: newsItems)
}
catch
{
if let context = self.managedObjectContext
{
context.delete(self)
}
throw error
}
}
}
public extension Source
{
@nonobjc class func fetchRequest() -> NSFetchRequest<Source>
{
return NSFetchRequest<Source>(entityName: "Source")
}
class func makeAltStoreSource(in context: NSManagedObjectContext) -> Source
{
let source = Source(context: context)
source.name = "SideStore Offical"
source.identifier = Source.altStoreIdentifier
source.sourceURL = Source.altStoreSourceURL
return source
}
class func fetchAltStoreSource(in context: NSManagedObjectContext) -> Source?
{
let source = Source.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Source.identifier), Source.altStoreIdentifier), in: context)
return source
}
}

View File

@@ -1,266 +0,0 @@
//
// StoreApp.swift
// AltStore
//
// Created by Riley Testut on 5/20/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
import CoreData
import Roxas
import AltSign
public extension StoreApp
{
#if ALPHA
static let altstoreAppID = "com.SideStore.SideStore"
#elseif BETA
static let altstoreAppID = "com.SideStore.SideStore"
#else
static let altstoreAppID = "com.SideStore.SideStore"
#endif
static let dolphinAppID = "me.oatmealdome.dolphinios-njb"
}
@objc
public enum Platform: UInt {
case ios
case tvos
case macos
}
extension Platform: Decodable {}
@objc
public final class PlatformURL: NSManagedObject, Decodable {
/* Properties */
@NSManaged public private(set) var platform: Platform
@NSManaged public private(set) var downloadURL: URL
private enum CodingKeys: String, CodingKey
{
case platform
case downloadURL
}
public init(from decoder: Decoder) throws
{
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
// Must initialize with context in order for child context saves to work correctly.
super.init(entity: PlatformURL.entity(), insertInto: context)
do
{
let container = try decoder.container(keyedBy: CodingKeys.self)
self.platform = try container.decode(Platform.self, forKey: .platform)
self.downloadURL = try container.decode(URL.self, forKey: .downloadURL)
}
catch
{
if let context = self.managedObjectContext
{
context.delete(self)
}
throw error
}
}
}
extension PlatformURL: Comparable {
public static func < (lhs: PlatformURL, rhs: PlatformURL) -> Bool {
return lhs.platform.rawValue < rhs.platform.rawValue
}
public static func > (lhs: PlatformURL, rhs: PlatformURL) -> Bool {
return lhs.platform.rawValue > rhs.platform.rawValue
}
public static func <= (lhs: PlatformURL, rhs: PlatformURL) -> Bool {
return lhs.platform.rawValue <= rhs.platform.rawValue
}
public static func >= (lhs: PlatformURL, rhs: PlatformURL) -> Bool {
return lhs.platform.rawValue >= rhs.platform.rawValue
}
}
public typealias PlatformURLs = [PlatformURL]
@objc(StoreApp)
public class StoreApp: NSManagedObject, Decodable, Fetchable
{
/* Properties */
@NSManaged public private(set) var name: String
@NSManaged public private(set) var bundleIdentifier: String
@NSManaged public private(set) var subtitle: String?
@NSManaged public private(set) var developerName: String
@NSManaged public private(set) var localizedDescription: String
@NSManaged public private(set) var size: Int32
@NSManaged public private(set) var iconURL: URL
@NSManaged public private(set) var screenshotURLs: [URL]
@NSManaged public var version: String
@NSManaged public private(set) var versionDate: Date
@NSManaged public private(set) var versionDescription: String?
@NSManaged public private(set) var downloadURL: URL
@NSManaged public private(set) var platformURLs: PlatformURLs?
@NSManaged public private(set) var tintColor: UIColor?
@NSManaged public private(set) var isBeta: Bool
@NSManaged public var sourceIdentifier: String?
@NSManaged public var sortIndex: Int32
/* Relationships */
@NSManaged public var installedApp: InstalledApp?
@NSManaged public var newsItems: Set<NewsItem>
@NSManaged @objc(source) public var _source: Source?
@NSManaged @objc(permissions) public var _permissions: NSOrderedSet
@nonobjc public var source: Source? {
set {
self._source = newValue
self.sourceIdentifier = newValue?.identifier
}
get {
return self._source
}
}
@nonobjc public var permissions: [AppPermission] {
return self._permissions.array as! [AppPermission]
}
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
{
super.init(entity: entity, insertInto: context)
}
private enum CodingKeys: String, CodingKey
{
case name
case bundleIdentifier
case developerName
case localizedDescription
case version
case versionDescription
case versionDate
case iconURL
case screenshotURLs
case downloadURL
case platformURLs
case tintColor
case subtitle
case permissions
case size
case isBeta = "beta"
}
public required init(from decoder: Decoder) throws
{
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
// Must initialize with context in order for child context saves to work correctly.
super.init(entity: StoreApp.entity(), insertInto: context)
do
{
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.bundleIdentifier = try container.decode(String.self, forKey: .bundleIdentifier)
self.developerName = try container.decode(String.self, forKey: .developerName)
self.localizedDescription = try container.decode(String.self, forKey: .localizedDescription)
self.subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle)
self.version = try container.decode(String.self, forKey: .version)
self.versionDate = try container.decode(Date.self, forKey: .versionDate)
self.versionDescription = try container.decodeIfPresent(String.self, forKey: .versionDescription)
self.iconURL = try container.decode(URL.self, forKey: .iconURL)
self.screenshotURLs = try container.decodeIfPresent([URL].self, forKey: .screenshotURLs) ?? []
let downloadURL = try container.decodeIfPresent(URL.self, forKey: .downloadURL)
let platformURLs = try container.decodeIfPresent(PlatformURLs.self.self, forKey: .platformURLs)
if let platformURLs = platformURLs {
self.platformURLs = platformURLs
// Backwards compatibility, use the fiirst (iOS will be first since sorted that way)
if let first = platformURLs.sorted().first {
self.downloadURL = first.downloadURL
} else {
throw DecodingError.dataCorruptedError(forKey: .platformURLs, in: container, debugDescription: "platformURLs has no entries")
}
} else if let downloadURL = downloadURL {
self.downloadURL = downloadURL
} else {
throw DecodingError.dataCorruptedError(forKey: .downloadURL, in: container, debugDescription: "E downloadURL:String or downloadURLs:[[Platform:URL]] key required.")
}
if let tintColorHex = try container.decodeIfPresent(String.self, forKey: .tintColor)
{
guard let tintColor = UIColor(hexString: tintColorHex) else {
throw DecodingError.dataCorruptedError(forKey: .tintColor, in: container, debugDescription: "Hex code is invalid.")
}
self.tintColor = tintColor
}
self.size = try container.decode(Int32.self, forKey: .size)
self.isBeta = try container.decodeIfPresent(Bool.self, forKey: .isBeta) ?? false
let permissions = try container.decodeIfPresent([AppPermission].self, forKey: .permissions) ?? []
self._permissions = NSOrderedSet(array: permissions)
}
catch
{
if let context = self.managedObjectContext
{
context.delete(self)
}
throw error
}
}
}
public extension StoreApp
{
@nonobjc class func fetchRequest() -> NSFetchRequest<StoreApp>
{
return NSFetchRequest<StoreApp>(entityName: "StoreApp")
}
class func makeAltStoreApp(in context: NSManagedObjectContext) -> StoreApp
{
let app = StoreApp(context: context)
app.name = "SideStore"
app.bundleIdentifier = StoreApp.altstoreAppID
app.developerName = "Side Team"
app.localizedDescription = "SideStore is an alternative App Store."
app.iconURL = URL(string: "https://user-images.githubusercontent.com/705880/63392210-540c5980-c37b-11e9-968c-8742fc68ab2e.png")!
app.screenshotURLs = []
app.version = "1.0"
app.versionDate = Date()
app.downloadURL = URL(string: "http://rileytestut.com")!
#if BETA
app.isBeta = true
#endif
return app
}
}

View File

@@ -1,27 +0,0 @@
//
// Benefit.swift
// AltStore
//
// Created by Riley Testut on 8/21/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
extension PatreonAPI
{
struct BenefitResponse: Decodable
{
var id: String
}
}
public struct Benefit: Hashable
{
public var type: ALTPatreonBenefitType
init(response: PatreonAPI.BenefitResponse)
{
self.type = ALTPatreonBenefitType(response.id)
}
}

View File

@@ -1,50 +0,0 @@
//
// Tier.swift
// AltStore
//
// Created by Riley Testut on 8/21/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
extension PatreonAPI
{
struct TierResponse: Decodable
{
struct Attributes: Decodable
{
var title: String
}
struct Relationships: Decodable
{
struct Benefits: Decodable
{
var data: [BenefitResponse]
}
var benefits: Benefits
}
var id: String
var attributes: Attributes
var relationships: Relationships
}
}
public struct Tier
{
public var name: String
public var identifier: String
public var benefits: [Benefit] = []
init(response: PatreonAPI.TierResponse)
{
self.name = response.attributes.title
self.identifier = response.id
self.benefits = response.relationships.benefits.data.map(Benefit.init(response:))
}
}

View File

@@ -1,14 +0,0 @@
//
// ALTAppPermission.h
// AltStore
//
// Created by Riley Testut on 7/23/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NSString *ALTAppPermissionType NS_TYPED_EXTENSIBLE_ENUM;
extern ALTAppPermissionType const ALTAppPermissionTypePhotos;
extern ALTAppPermissionType const ALTAppPermissionTypeBackgroundAudio;
extern ALTAppPermissionType const ALTAppPermissionTypeBackgroundFetch;

View File

@@ -1,13 +0,0 @@
//
// ALTAppPermission.m
// AltStore
//
// Created by Riley Testut on 7/23/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import "ALTAppPermission.h"
ALTAppPermissionType const ALTAppPermissionTypePhotos = @"photos";
ALTAppPermissionType const ALTAppPermissionTypeBackgroundAudio = @"background-audio";
ALTAppPermissionType const ALTAppPermissionTypeBackgroundFetch = @"background-fetch";

View File

@@ -1,13 +0,0 @@
//
// ALTPatreonBenefitType.h
// AltStore
//
// Created by Riley Testut on 8/27/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NSString *ALTPatreonBenefitType NS_TYPED_EXTENSIBLE_ENUM;
extern ALTPatreonBenefitType const ALTPatreonBenefitTypeBetaAccess;
extern ALTPatreonBenefitType const ALTPatreonBenefitTypeCredits;

View File

@@ -1,12 +0,0 @@
//
// ALTPatreonBenefitType.m
// AltStore
//
// Created by Riley Testut on 8/27/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import "ALTPatreonBenefitType.h"
ALTPatreonBenefitType const ALTPatreonBenefitTypeBetaAccess = @"1186336";
ALTPatreonBenefitType const ALTPatreonBenefitTypeCredits = @"1186340";

View File

@@ -1,12 +0,0 @@
//
// ALTSourceUserInfoKey.h
// AltStore
//
// Created by Riley Testut on 11/4/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NSString *ALTSourceUserInfoKey NS_TYPED_EXTENSIBLE_ENUM;
extern ALTSourceUserInfoKey const ALTSourceUserInfoKeyPatreonAccessToken;

View File

@@ -1,11 +0,0 @@
//
// ALTSourceUserInfoKey.m
// AltStore
//
// Created by Riley Testut on 11/4/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import "ALTSourceUserInfoKey.h"
ALTSourceUserInfoKey const ALTSourceUserInfoKeyPatreonAccessToken = @"patreonAccessToken";

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